Search code examples
c++templatesc++20c++-concepts

How do I check that a particular template parameter is the same


I have a class with three template parameters:

template<typename A, typename B, typename C>
class Unit;

Then I have a concept representing this class and all its specialization:

template <typename T>
struct is_unit : std::false_type {};

template<typename A, typename B, typename C>
struct is_unit<Unit<A, B, C>> : std::true_type {};

template <typename T>
constexpr bool is_unit_v = is_unit<T>::value;

template <typename T>
concept Unit_T = is_unit_v<T>;

In the definition of the Unit class, I want a function that returns a Unit with a different specialization from the one through which the function is called. I want the user to provide the desired return type. I have this working so far:

template<typename A, typename B, typename C>
class Unit {
public:
    // Other member stuff...

    template<Unit_T U>
    U as() { return U(*this); }
}

Unit<MyType, long double, MyOtherType> unit;
// Do stuff to unit.
auto unit2 = unit.as<Unit<MyType, long int, AnotherType>();

That all works as expected. There's one more requirement, however, that I can't figure out how to implement. The first template parameter of the desired type must match the first template parameter of the type through which it was called. So this:

Unit<MyType, long double, MyOtherType> unit;
// Do stuff to unit.
auto unit2 = unit.as<Unit<YetAnotherType, long int, AnotherType>();

should not compile.

I would think that the correct way to do this would look something like:

template<typename A, typename B, typename C>
class Unit {
public:
    // Other member stuff...

    template<Unit_T U>
    requires std::is_same_v<U<D>, D>
    // or maybe std::is_same_V<U::D, D> ?
    U as() { return U(*this); }
}

But that doesn't work. And, as I understand, even if that was the right way to require a template parameter be the right type, I can't further constrain a concept.

I tried writing another concept for a Unit with a specific first template parameter:

template<typename U, typename D>
concept UnitFirstType = is_unit_v<U> && std::is_same_v<U<D> /* or U::D */, D>;

But that doesn't work.

The problem seems to lie in how I'm using std::is_same_v. I just don't know how to use it with a template parameter.

So what is the proper way to achieve this:

Unit<MyType, long double, MyOtherType> unit;
auto unit2 = unit.as<Unit<MyType, long int, AnotherType>(); // will compile
// auto unit3 = unit.as<Unit<YetAnotherType, long int, AnotherType>(); // should not compile

Solution

  • This is probably what you want

    template<typename A, typename B, typename C>
    class Unit;
    
    template<typename U, typename A>
    constexpr bool unit_first_type = false;
    
    template<typename A, typename B, typename C>
    constexpr bool unit_first_type<Unit<A, B, C>, A> = true;
    
    template<typename U, typename A>
    concept UnitFirstType = unit_first_type<U, A>;
    
    template<typename A, typename B, typename C>
    class Unit {
     public:
      // Other member stuff...
      template<UnitFirstType<A> U>
      U as();
    };
    

    Demo