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

Time:05-29

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

CodePudding user response:

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

CodePudding user response:

I suppose you can use the good-old (pre C 20) SFINAE.

For example, given a custom type traits as follows

template <typename, typename>
struct firstEqual : public std::false_type
{};

template <typename A, typename B1, typename C1, typename B2, typename C2>
struct firstEqual<Unit<A, B1, C1> const, Unit<A, B2, C2> const>
   : public std::true_type
{};

you can enable/disable as() as follows

template <typename U>
std::enable_if_t<firstEqual<Unit<A, B, C> const, U const>::value, U>
   as() { return U{}; }

Observe the use of const, to accept the case of different Units with different constness but the same first template argument... just in case is what do you want.

Observe also that you don't need the Unit_T concept any more.

The following is a full compiling example

#include <type_traits>

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

template <typename, typename>
struct firstEqual : public std::false_type
{};

template <typename A, typename B1, typename C1, typename B2, typename C2>
struct firstEqual<Unit<A, B1, C1> const, Unit<A, B2, C2> const>
   : public std::true_type
{};

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

    template <typename U>
    std::enable_if_t<firstEqual<Unit<A, B, C> const, U const>::value, U>
       as() { return U{}; }
};



int main()
{
  Unit<int, long double, void> unit;

  auto unit2 = unit.as<Unit<int, long, float>>();  // compile
  //auto unit3 = unit.as<Unit<char, long, float>>(); // compilation error
}
  • Related