2017-07-25 48 views
7

我试图检测模板参数的成员函数baz()的存在:为什么decltype(declval <T>().func())在decltype(&T :: func)不工作的地方工作?

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 { }; 

当然,现在这可以onl y检测具有0个参数的baz函数。 为什么在使用declval<T>().baz()而不是decltype(&T::baz)时正确选择了专业化?

+0

只是一个猜测:如果删除的通用模板默认''= void''会发生什么? – bjhend

+0

然后'ImplementsBaz :: value'是非法的:'太少模板arguments' – jtbandes

+0

啊,是的,我确实忽略了。 – bjhend

回答

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,以便使用专业化。

+0

我想我不明白为什么会导致部分专业化不被使用 - 你知道为什么/知道解释它的任何资源吗? – jtbandes

+0

@jtbandes当然,请参阅编辑 –

+0

谢谢,有道理:_“就好像你已经写了'has_member :: value'。现在,模板参数列表与模板has_member的任何特化相比较,只有没有专门化匹配时,主模板的定义被用作回退。“_ – jtbandes

1

这是因为decltype(&T::baz)是一个错误,部分专业化从未实例化。在T(即Bar)中没有称为baz的静态成员。

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


如果要检测方法的存在性,而不管传递给它的参数是否只有一个过载。

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。基本上,它假设存在一个具有该名称的方法,然后如果有另一个具有该名称的方法,那么特征类将出错,并选择专业化或类似的权利true_type。看一看!

+0

我认为你的第一个答案其实是对的 - 看起来返回类型确实很重要。 '&Bar :: baz'应该是一个有效的表达式,那么有没有办法使用'decltype(&T :: baz)'来做到这一点? – jtbandes

+0

即使baz变得静止,依然存在的差异依然存在。 – jwimberley

+1

@jwimberley表达式求到一个地址,这仍然不是类型的这种情况下'void' – Curious

2

尝试用

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因为baz()回报int不匹配void获得false

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

所以解决方案(以及...一个可能的解决方案)是使用decltype(&T::baz, void())因为回报void如果T::baz存在(或失败,返回任何结果,如果T::baz不存在)。

+0

这可能是最好的,除非用户只想匹配具有void返回类型的函数T :: baz。 – jwimberley

+1

@jwimberley - 会是一个不同的问题;在这种情况下,OP解决方案是一个很好的解决方案,但只有当'baz()'没有收到任何参数(或者如果开发人员知道参数的类型)时,问题才会起作用。 – max66

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 { }; 

这只会工作,如果它只是你打算用返回void类型匹配功能T::baz ,尽管您的备用工作解决方案也是如此。它也有缺陷,只有在没有参数的情况下才能工作,所以不幸的是,它与风格中的第二种解决方案只有不同。

相关问题