我试图在模板参数中检测到成员函数baz()
的存在:
template<typename T, typename = void>
struct ImplementsBaz : public std::false_type { };
template<typename T>
struct ImplementsBaz<T, decltype(&T::baz)> : public std::true_type { };
但它总是产生错误:
struct Foo {};
struct Bar { void baz() {} };
std::cout << ImplementsBaz<Foo>::value << std::endl; // 0
std::cout << ImplementsBaz<Bar>::value << std::endl; // also 0
使用declval
和调用,该方法确实有效:
template<typename T>
struct ImplementsBaz<T, decltype(std::declval<T>().baz())> : public std::true_type { };
当然,现在这只能检测带有0个参数的baz
函数。 为什么在使用declval<T>().baz()
而非decltype(&T::baz)
时正确选择了专业化?
答案 0 :(得分:3)
尝试
decltype(&T::baz, void())
decltype(std::declval<T>().baz())
和
struct Bar { void baz() {} };
有效,因为baz()
返回void
,因此void
与非专业typename = void
结构中的默认Implements_baz
匹配。
但是,如果您将Bar
定义如下
struct Bar { int baz() { return 0; } };
您从false
获得Implement_baz
,因为baz()
返回的int
与void
不匹配。
与decltype(&T::baz)
相同的问题:与void
不匹配,因为返回方法的类型。
所以解决方案(以及......可能的解决方案)是使用decltype(&T::baz, void())
,因为如果存在void
则返回T::baz
(或者如果T::baz
则失败,则不返回任何内容不存在)。
答案 1 :(得分:3)
如果您使用void_t
“检测惯用语”,那么 按预期工作:
template <typename...> using void_t = void;
template <typename T>
struct ImplementsBaz<T, void_t<decltype(&T::baz)>> : std::true_type {};
struct Bar { void baz() {} };
static_assert(ImplementsBaz<Bar>::value); // passes
至于为什么,this question详细解释了“void_t
技巧”的工作原理。引用接受的答案:
就像你写了
has_member<A, void>::value
一样。现在,将模板参数列表与模板has_member
的任何特化进行比较。只有在没有专门化匹配时,主模板的定义才会用作后备。
在原始情况下,decltype(&T::baz)
不是void
,因此专业化与原始模板不匹配,因此不予考虑。我们需要使用void_t
(或某些其他机制,例如强制转换)将类型更改为void
,以便使用专门化。
答案 2 :(得分:1)
这是因为decltype(&T::baz)
是一个错误,部分特化永远不会被实例化。 baz
中没有名为T
的静态成员(即Bar
)。
第二个做正确的事情,即在一个实例上调用该方法,然后使用它的返回类型。
如果只想有一个重载,无论传递给它的参数是什么,都要检测方法是否存在。
template <typename Type, typename = std::enable_if_t<true>>
struct ImplementsBaz : public std::integral_constant<bool, true> {};
template <typename Type>
struct ImplementsBaz<Type, std::enable_if_t<
std::is_same<decltype(&T::baz), decltype(&T::baz)>
::value>>
: public std::integral_constant<bool, false> {};
如果要检测该方法是否包含重载,请查看member detection idiom。基本上它假定存在具有该名称的方法,然后如果存在具有该名称的另一个方法,则traits类会出错并选择正确的true_type
特化或类似。看看吧!
答案 3 :(得分:0)
另一种可能的解决方案是使用
template<typename T>
struct ImplementsBaz<T, typename std::result_of<decltype(&T::baz)(T)>::type > : public std::true_type { };
或者,如果您更喜欢可读性,
template<typename T>
using BazResult = typename std::result_of<decltype(&T::baz)(T)>::type;
template<typename T>
struct ImplementsBaz<T, BazResult<T> > : public std::true_type { };
只有当您打算将函数T::baz
与void返回类型匹配时,这才有效,尽管您的备用工作解决方案也是如此。它还有缺点,只有在没有参数的情况下才能工作,所以不幸的是它只与你的第二种风格解决方案不同。