Why does decltype (declval <T> (). Func ()) work, where decltype (& T :: func) doesn't?
I tried to detect the presence of the baz() member function in the template parameter:
template<typename T, typename = void> struct ImplementsBaz : public std::false_type { }; template<typename T> struct ImplementsBaz<T, decltype(&T::baz)> : public std::true_type { }; But it always calls false:
struct Foo {}; struct Bar { void baz() {} }; std::cout << ImplementsBaz<Foo>::value << std::endl; // 0 std::cout << ImplementsBaz<Bar>::value << std::endl; // also 0 Using declval and calling the method really works:
template<typename T> struct ImplementsBaz<T, decltype(std::declval<T>().baz())> : public std::true_type { }; Of course, now it can detect the baz function with 0 arguments. Why is the specialization correctly selected when using declval<T>().baz() but not decltype(&T::baz) ?
If you use the discovery identifier void_t ", then it works as expected:
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 In this regard, this question explains in detail how the void_t trick void_t . To quote from the accepted answer:
As if you wrote
has_member<A, void>::value. Now the list of template parameters is compared with any specializations of thehas_membertemplate. Only if no specialization matches the definition of the primary template is used as a recession.
In the original case, decltype(&T::baz) not void , so specialization does not match the original template and therefore is not taken into account. We need to use void_t (or some other mechanism, such as casting) to change the type to void so that specialization is used.
Try
decltype(&T::baz, void()) Your example with decltype(std::declval<T>().baz()) and
struct Bar { void baz() {} }; works because baz() returns void , so void matches the default typename = void in the non-specialized Implements_baz structure.
But if you define Bar as follows
struct Bar { int baz() { return 0; } }; you get false from Implement_baz because baz() returns an int that does not match void .
The same problem with decltype(&T::baz) : does not match void , because it returns the type of the method.
So the solution (well ... a possible solution) is used by decltype(&T::baz, void()) , because return void if T::baz exists (or fails, and returns nothing if T::baz does not exist).
This is because decltype(&T::baz) is a mistake, and partial specialization is never created. There is no static member in T called baz (i.e. Bar ).
The second does the right thing, i.e. calls the method on the instance and then uses the return type.
If you want to detect the presence of a method, no matter what parameters you pass to it, if there is only one overload.
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> {}; If you want to detect the presence of this method, if it contains overloads, look at the element detection identifier . It is generally assumed that there is a method with this name, and then, if there is another method with this name, the attribute class goes into error and selects the correct specialization true_type or similar. Take a look!
Another possible solution is to use
template<typename T> struct ImplementsBaz<T, typename std::result_of<decltype(&T::baz)(T)>::type > : public std::true_type { }; Or, if you prefer to read,
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 { }; This will only work if you intend to map the T::baz functions to the return type, although the same is true for your alternative working solution. This also has the disadvantage of only working if there are no parameters, so it unfortunately differs from your second style decision.