What does template<typename T, T> mean?
Asked Answered
S

3

9

I was reading this prehistoric metaprogam example to detect whether a class supports member find. (or any other member).

template<typename T>
class DetectFind
{
    struct Fallback { int find; }; 
    struct Derived : T, Fallback { };

    template<typename U, U> struct Check;

    typedef char Yes[1];  
    typedef char No[2]; 

    template<typename U>
    static No& func(Check<int Fallback::*, &U::find>*);

    template<typename U>
    static Yes& func(...);

public:
    typedef DetectFind type;
    enum { value = sizeof(func<Derived>(0)) == sizeof(Yes) };
};

int main()
{
    std::cout << DetectFind<std::vector<int> >::value << std::endl;
    std::cout<< DetectFind<std::set<int> >::value << std::endl;
}

Intuitively I do understand the aim behind this, but if someone would ask me to write same thing from scratch after 10 days, I maybe will fail this.
The reason for that is that I do not fully understand the syntactical and language stretch that is used here.
Can someone please explain what the following syntaxes mean?

  1. Check<int Fallback::*, &U::find>* (I know its trying to benefit from SFIANE here, but how is this detecting the existence of find, I beleive this is linked to the second question as well)
  2. template<typename U, U> struct Check;

The program outputs 0 1 as predicted;

Swart answered 31/8, 2021 at 9:40 Comment(0)
B
2

First, let us consider the struct Derived. Since it derives from Fallback it certainly contains a int field find, and possibly a member function find, whose existence is what you want to check.

As noted in the answer above, in the declaration of the struct Check, the first template parameter is as type, and the second is a non-type parameter, of the type given by the first parameter.

Given that, let us examine the two overloads of func. The first overload takes a pointer to a Check struct, whose first template parameters is a type equal to pointer-to-int member of Fallback (int Fallback::*). The second template parameter is then interpreted as a pointer-to-int member with value is &U::find. Given U = Derived, if T contains a find member function, this second parameter of Check is ambiguous, as it could also refer to the int find inherited by Fallback. By SFINAE, this overload of func will be then discarded.

The second func overload is always well defined. But if the first overload is not discarded, the second one is less specialized, so the the compiler will choose the first overload.

In summary: if U in template <typename U> func contains a member function find, the compiler chooses the second overload of func. If the member function find is absent, the compiler chooses the first overload.

Finally, the value of DetectFind is determined by the size of the returning type of the chosen func, which depending on the overload is a char array of size 1 or 2. From there, you get which overload of func is chosen, and from the discussion above, whether T has a member function find or not.

Border answered 31/8, 2021 at 10:36 Comment(0)
D
7

template<typename U, U> means there are two template arguments: an arbitrary type U, and an unnamed non-type template argument (eg. a value argument) whose type is U. As an example, an int scenario would be ClassName<int,42>.

In your example, the type U is a pointer to int member, and the value is the address of an int member.

Djerba answered 31/8, 2021 at 10:2 Comment(0)
B
2

First, let us consider the struct Derived. Since it derives from Fallback it certainly contains a int field find, and possibly a member function find, whose existence is what you want to check.

As noted in the answer above, in the declaration of the struct Check, the first template parameter is as type, and the second is a non-type parameter, of the type given by the first parameter.

Given that, let us examine the two overloads of func. The first overload takes a pointer to a Check struct, whose first template parameters is a type equal to pointer-to-int member of Fallback (int Fallback::*). The second template parameter is then interpreted as a pointer-to-int member with value is &U::find. Given U = Derived, if T contains a find member function, this second parameter of Check is ambiguous, as it could also refer to the int find inherited by Fallback. By SFINAE, this overload of func will be then discarded.

The second func overload is always well defined. But if the first overload is not discarded, the second one is less specialized, so the the compiler will choose the first overload.

In summary: if U in template <typename U> func contains a member function find, the compiler chooses the second overload of func. If the member function find is absent, the compiler chooses the first overload.

Finally, the value of DetectFind is determined by the size of the returning type of the chosen func, which depending on the overload is a char array of size 1 or 2. From there, you get which overload of func is chosen, and from the discussion above, whether T has a member function find or not.

Border answered 31/8, 2021 at 10:36 Comment(0)
K
1

Let's firstly cover the declaration of struct Check; template<typename U,U> struct Check means a that template arguments are

  1. An ordinary type U
  2. An object of type U, which doesn't have name.

Check<int Fallback::*, &U::find>* is a little bit more confusing.

The first template argument is a pointer to member., in this case it stands for a pointer to an member of class Fallback of type int, which would have allowed us to write something like this.

Fallback obj{10};
int Fallback::* find_ptr = &Fallback::find;
std::cout << obj.*find << std::endl;   //prints 10, note: ".*" is a separate operator

The second argument of the template is the address of the member of class U, as the initial declaration of struct Check suggested.

Kavita answered 31/8, 2021 at 10:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.