为什么 decltype(declval<T>().func()) 在 decltype(&T::func) 不工作的地方工作?

Why does decltype(declval<T>().func()) work where decltype(&T::func) doesn't?

我试图检测模板参数中成员函数 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 { };

当然,现在这只能检测一个 baz 参数为 0 的函数。 为什么使用 declval<T>().baz() 时选择的专业是正确的,而不是 decltype(&T::baz)

这是因为 decltype(&T::baz) 是一个错误,并且永远不会实例化偏特化。 T 中没有名为 baz 的静态成员(即 Bar)。

第二个做了正确的事情,即在实例上调用方法,然后使用它的 return 类型。


如果只有一个重载,无论传递给它的参数是什么,你都想检测方法是否存在。

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。基本上它假设存在一个具有该名称的方法,然后如果存在另一个具有该名称的方法,则特征 class 会出错并选择正确的 true_type 专业化或类似的。快来看看吧!

试试

decltype(&T::baz, void())

你的例子 decltype(std::declval<T>().baz())

struct Bar { void baz() {} };

之所以有效,是因为 baz() return void 所以 void 匹配未专门化的 Implements_baz 结构中的默认值 typename = void

但是如果你定义Bar如下

struct Bar { int baz() { return 0; } };

您从 Implement_baz 获得 false 因为 baz() return intvoid.

不匹配

decltype(&T::baz) 相同的问题:不匹配 void 因为 return 方法的类型。

所以解决方案(嗯......一个可能的解决方案)是使用 decltype(&T::baz, void()) 因为 return void 如果 T::baz 存在(或失败,并且 return 没有,如果 T::baz 不存在)。

如果您使用 void_t "detection idiom",那么它 会按预期工作:

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

Godbolt link

至于为什么this question详细解释了“void_t技巧”的工作原理。引用已接受的答案:

It's as if you had written has_member<A, void>::value. Now, the template parameter list is compared against any specializations of the template has_member. Only if no specialization matches, the definition of the primary template is used as a fall-back.

在原来的情况下,decltype(&T::baz)不是void,所以专业化与原来的模板不匹配,所以不考虑。我们需要使用 void_t(或其他一些机制,例如强制转换)将类型更改为 void,以便使用特化。

另一种可能的解决方案是使用

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 return 类型匹配时才有效,尽管您的替代工作解决方案也是如此。它也有只有在没有参数的情况下才能工作的不足,所以它只是在风格上与你的第二个解决方案不同,不幸的是。