为什么decltype(declval <t>()。func())在decltype(&amp; T :: func)不起作用的情况下工作?

时间:2017-07-25 17:51:02

标签: c++ c++11 template-specialization sfinae member-functions

我试图在模板参数中检测到成员函数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)时正确选择了专业化?

4 个答案:

答案 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()返回的intvoid不匹配。

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

Godbolt link

至于为什么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返回类型匹配时,这才有效,尽管您的备用工作解决方案也是如此。它还有缺点,只有在没有参数的情况下才能工作,所以不幸的是它只与你的第二种风格解决方案不同。