Home > Mobile >  Constrain template template parameter to be one of two types
Constrain template template parameter to be one of two types

Time:12-21

I have the following classes:

template <typename T, int N0, int N1, int N2>
struct A{};

template <typename T, int N0, int N1, int N2>
struct B{};

I want templated functions to only be able to take one of these two types:

template <typename AorB>
void foo(AorB& arg)
{
}

Where all A<T,N0,N1,N2> and B<T,N0,N1,N2> are accepted. What is the best way to approach this?

Edit: This is intended to work for base-classes where inheritance is used. A<...> would have some derived-class A_derived<N0, N1, N2>, and B<...> would have some derived-class B_derived<N0, N1, N2>. How would AorB be constrained to only be of type A<...> or B<...>?

CodePudding user response:

You could define a helper variable like this:

template<typename T>
constexpr static bool is_ab = false;

template <typename T, int N0, int N1, int N2>
constexpr static bool is_ab<A<T, N0, N1, N2>> = true;

template <typename T, int N0, int N1, int N2>
constexpr static bool is_ab<B<T, N0, N1, N2>> = true;

And then the function foo for either is_ab or not:

template <typename T, std::enable_if_t<is_ab<T>, int> = 0>
void foo(T arg)
{
    std::cout << "AB\n";
}

template <typename T, std::enable_if_t<(!is_ab<T>), int> = 0>
void foo(T arg)
{
    std::cout << "not AB\n";
}

If you need other types use the first overload, you can just define the is_ab for them as shown.

Full example here.

Or, as @NathanOliver commented, use a concept.

CodePudding user response:

A simple way to achieve what you want would be to define a type with a specified name inside your base classes in this case is_ab which then will be tested via SFINAE.

template <typename T, int N0, int N1, int N2>
struct A{using is_ab = int;};//the type doesn't matter

template <typename T, int N0, int N1, int N2>
struct B{using is_ab = int;};

and to test for is_ab member we use

template<typename T, typename = void> struct is_ab : std::false_type{};
template<typename T> struct is_ab<T, std::void_t<decltype(typename T::is_ab{})>> : std::true_type{};
template<typename T> constexpr bool is_ab_v = is_ab<T>::value;

now it is as simple as

template <int N0, int N1, int N2>
struct AD: A<int, N0, N1, N2>{};

template <int N0, int N1, int N2>
struct BD: B<int, N0, N1, N2>{};

template<typename T>
auto foo(T& a)
{
    static_assert(is_ab_v<T>);
    //...
}

int main()
{
    A<int,1,2,3> a;
    B<bool,3,4,5> b;
    AD<3,4,3> ad;
    BD<3,2,1> bd;
    int r = 4;

    foo(a);
    foo(b);
    foo(ad);
    foo(bd);
    //foo(r); fail
}

This wont work if your inheritance is protected/private and will trigger the assert.

CodePudding user response:

Florestan's answer is best for your question, but I think it could be a alternative.

#include <utility>

template <auto v>
struct w {};

template <typename T, typename w1, typename w2, typename w3>
struct A {};

template <typename T, typename w1, typename w2, typename w3>
struct B {};

// and derived A, B

template <typename T, typename w1, typename w2, typename w3>
struct DerivedA : public A<T, w1, w2, w3> {};

template <typename T, typename w1, typename w2, typename w3>
struct DerivedB : public B<T, w1, w2, w3> {};

This approach is because I don't know how to separate type and non-type template parameters together.

template <
    template <typename... Args> typename Declare,
    typename... Arguments_pack
>
constexpr bool is_AorB_type(Declare<Arguments_pack...> type) // separate declare and argument
{
    return std::is_same<Declare<Arguments_pack...>, A<Arguments_pack...>>::value
        || std::is_same<Declare<Arguments_pack...>, B<Arguments_pack...>>::value;
}

template <typename AorB>
concept concept_is_AorB_type = (is_AorB_type(*(AorB*)(0)));

template <typename AorB>
requires concept_is_AorB_type<AorB>
void foo(AorB& arg) {}

for test

void first_test()
{
    constexpr A<int, w<0>, w<0>, w<0>> palk = *(A<int, w<0>, w<0>, w<0>>*)(0);
    constexpr bool palk_result = is_AorB_type(palk);
    using type2 = std::enable_if<is_AorB_type(palk)>::type;

    A<int, w<0>, w<0>, w<0>> a;
    B<int, w<0>, w<0>, w<0>> b;
    DerivedA<int, w<0>, w<0>, w<0>> da;
    DerivedB<int, w<0>, w<0>, w<0>> db;
    constexpr bool a_is = is_AorB_type(a);      // true
    constexpr bool b_is = is_AorB_type(b);      // true
    constexpr bool da_is = is_AorB_type(da);    // false
    constexpr bool db_is = is_AorB_type(db);    // false
    foo(a);
    foo(b);
    foo(da); // error
    foo(db); // error
}

test for different length

template <typename A, typename B>
struct structs_of_different_lengths{};
void other_test()
{
    structs_of_different_lengths<w<0>, w<0>> sdl;
    foo(sdl); // error
}
  • Related