Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does decltype(declval<T>().func()) work where decltype(&T::func) doesn't?

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)?

like image 301
jtbandes Avatar asked Jul 25 '17 17:07

jtbandes


People also ask

How does decltype work in C++?

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.

What is std :: Declval?

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.

What does decltype return?

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.

What does decltype auto do?

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.


2 Answers

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 template has_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.

like image 161
Tristan Brindle Avatar answered Sep 21 '22 07:09

Tristan Brindle


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).

like image 31
max66 Avatar answered Sep 25 '22 07:09

max66