How to use a trait type as an argument to an optionally compiled member function of a class template?

Viewed 42

I would like to define a member function in a class template only if traits contain type and use type as its argument like that:

struct A {};
struct B { using type = int; };

template <typename T>
concept has_type = requires { typename T::type; };

template <typename Traits>
struct Handler
{
    void set(typename Traits::type)
        requires has_type<Traits>
    {}
};

Unfortunately, typename Traits::type is parsed before the requires clause and it fails to compile if type does not exist (struct A):

<source>:19:14: error: no type named 'type' in 'struct A'
   19 |         void set(typename Traits::type)

I could only come up with this alternative, which looks excessively cumbersome:

template <typename Traits>
struct Handler
{
    template <typename Type>
        requires has_type<Traits> && std::same_as<Type, typename Traits::type>
    void set(Type)
    {}
};

It also fails to compile under clang (although I expected it to short-circuit), but gcc compiles it correctly:

<source>:20:78: error: no type named 'type' in 'A'
            requires has_type<Traits> && std::same_as<Type, typename Traits::type>

Full example.

Questions

  1. What is the idiomatic way to define set depending on whether Traits class contains type?
  2. Why does clang try to evaluate std::same_as if requires has_type failed?
  3. Who is right, clang or gcc?
1 Answers

A class template member function is constrainable, but is not SFINAE-aware. The idiomatic solution (which, inter alia, works even under C++17) is to make set a (class template member) function template accessing the type member type alias via (type-dependent) SFINAE context:

template<int..., class U = Traits>
void set(typename U::type);

or:

template<std::same_as<Traits> U = Traits>
void set(typename U::type);

Example.

wrt 2., 3., see How to use a trait type as an argument to an optionally compiled member function of a class template?

Related