Reputation: 118671
I was trying to detect the presence of a member function baz()
in a 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 produces 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 does work, though:
template<typename T>
struct ImplementsBaz<T, decltype(std::declval<T>().baz())> : public std::true_type { };
Of course, now this can only detect a baz
function with 0 arguments. Why is the specialization correctly selected when using declval<T>().baz()
, but not decltype(&T::baz)
?
Upvotes: 11
Views: 4123
Reputation: 66200
Try with
decltype(&T::baz, void())
Your example with decltype(std::declval<T>().baz())
and
struct Bar { void baz() {} };
works because baz()
return void
so the void
match the default typename = void
in the not specialized Implements_baz
struct.
But if you define Bar
as follows
struct Bar { int baz() { return 0; } };
you obtain false
from Implement_baz
because baz()
return int
that doesn't match void
.
Same problem with decltype(&T::baz)
: doesn't match void
because return the type of a method.
So the solution (well... a possible solution) is use decltype(&T::baz, void())
because return void
if T::baz
exist (or fail, and return nothing, if T::baz
doesn't exist).
Upvotes: 3
Reputation: 16824
If you use the void_t
"detection idiom", then it does work 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
As to why, this question explains in detail how the "void_t
trick" works. To quote from the accepted answer:
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.
In the original case, decltype(&T::baz)
is not void
, so the specialization does not match the original template and so is not considered. We need to use void_t
(or some other mechanism, such as a cast) to change the type to void
so that the specialisation will be used.
Upvotes: 6
Reputation: 21510
This is because decltype(&T::baz)
is an error and the partial specialization is never instantiated. There is no static member called baz
in T
(i.e. Bar
).
The second one does the right thing, i.e. call the method on an instance and then use the return type of that.
If you want to detect the presence of the method regardless of 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 that method if it contains overloads, take a look at the member detection idiom. Basically it assumes that a method with that name exists and then if there is another method with that name then the traits class goes into error and selects the right true_type
specialization or similar. Take a look!
Upvotes: 1
Reputation: 1748
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 for readability,
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 it just your intention to match functions T::baz
with a void return type, although the same is true of your alternate working solution. It also has the deficiency of only working if there are no parameters, so it is only different from your second solution in style, unfortunately.
Upvotes: 0