我试图检测模板参数中是否存在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)
?
如果您使用"检测习惯用法"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
神螺栓链接
至于为什么,这个问题详细解释了"void_t
技巧"是如何工作的。引用公认的答案:
就好像你写了
has_member<A, void>::value
.现在,将模板参数列表与模板has_member
的任何专用化进行比较。仅当没有专用化匹配时,主模板的定义才用作回退。
在原始情况下,decltype(&T::baz)
不是void
,因此专业化与原始模板不匹配,因此不考虑。我们需要使用void_t
(或其他一些机制,例如强制转换(将类型更改为void
以便使用专用化。
尝试使用
decltype(&T::baz, void())
您的示例与decltype(std::declval<T>().baz())
和
struct Bar { void baz() {} };
工作是因为baz()
返回void
因此void
与非专用Implements_baz
结构中的默认typename = void
匹配。
但是,如果您按如下方式定义Bar
struct Bar { int baz() { return 0; } };
您从Implement_baz
获得false
baz()
因为返回的int
与void
不匹配。
与decltype(&T::baz)
相同的问题:不匹配void
因为返回方法的类型。
所以解决方案(嗯...一个可能的解决方案(是使用decltype(&T::baz, void())
因为如果T::baz
存在,则返回void
(如果不存在,则返回T::baz
失败,并且不返回任何内容(。
这是因为decltype(&T::baz)
是一个错误,并且永远不会实例化部分专用化。T
中没有称为baz
的静态成员(即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> {};
如果要检测该方法是否存在(如果它包含重载(,请查看成员检测习惯用法。 基本上,它假设存在具有该名称的方法,然后如果存在具有该名称的另一个方法,则 traits 类将出错并选择正确的专用化或类似true_type
。 看一看!
另一种可能的解决方案是使用
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 返回类型匹配时,这才有效,尽管您的备用工作解决方案也是如此。它还具有仅在没有参数时才起作用的缺点,因此不幸的是,它仅在样式上与您的第二种解决方案不同。