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)
?
In the C++ programming language, decltype is a keyword used to query the type of an expression. Introduced in C++11, its primary intended use is in generic programming, where it is often difficult, or even impossible, to express types that depend on template parameters.
std::declvalReturns an rvalue reference to type T without referring to any object. This function shall only be used in unevaluated operands (such as the operands of sizeof and decltype ). T may be an incomplete type.
decltype returnsIf what we pass to decltype is the name of a variable (e.g. decltype(x) above) or function or denotes a member of an object ( decltype x.i ), then the result is the type of whatever this refers to.
decltype(auto) is primarily useful for deducing the return type of forwarding functions and similar wrappers, where you want the type to exactly “track” some expression you're invoking.
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
Godbolt link
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.
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With