为什么 decltype(declval()<T>.func()) 可以工作,而 decltype(&T::func) 不能工作?



我试图检测模板参数中是否存在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获得falsebaz()因为返回的intvoid不匹配。

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 返回类型匹配时,这才有效,尽管您的备用工作解决方案也是如此。它还具有仅在没有参数时才起作用的缺点,因此不幸的是,它仅在样式上与您的第二种解决方案不同。

最新更新