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();
};
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 Unit
s 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
}