为什么 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 int
与 void
.
不匹配
与 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
至于为什么,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 类型匹配时才有效,尽管您的替代工作解决方案也是如此。它也有只有在没有参数的情况下才能工作的不足,所以它只是在风格上与你的第二个解决方案不同,不幸的是。
我试图检测模板参数中成员函数 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 int
与 void
.
与 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
至于为什么,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 templatehas_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 类型匹配时才有效,尽管您的替代工作解决方案也是如此。它也有只有在没有参数的情况下才能工作的不足,所以它只是在风格上与你的第二个解决方案不同,不幸的是。