multiple default template argument cases that depend on a template argument respectively

53 Views Asked by At

I would like to define two particular default cases for a template class A.

Is something similar possible?:

template<typename T1, typename T2>
class A{
    // ...
};

struct X;
// if necessary, I can also define X right here.

template<>
A<X> = A<X,int>;

template<typename T>
A<T> = A<T,T>;

int main(){
    A<X> a;     // shall construct an instance of A<X,int>
    A<float> b; // shall construct an instance of A<float,float>
}

I see how it can be done by using a derived of A. However, I would hope that it is also possible in a similarly straight-forward way to the one presented in the non-functioning snippet above.

3

There are 3 best solutions below

1
Pepijn Kramer On BEST ANSWER

You can use template specialization to help you deduce the type of your second argument (this can easily be extended to other specializations too):

#include <type_traits>

namespace details
{
    // in allmost all cases we want to imply type int for the second argument
    template<typename type_t>
    struct deduced_second_arg_t_helper
    {
        using type = int;
    };

    // except for float, so use a template specialization to deduce a float
    template<>
    struct deduced_second_arg_t_helper<float>
    {
        using type = float;
    };

    // shorthand like 
    template<typename type_t>
    using deduced_second_arg_t = typename deduced_second_arg_t_helper<type_t>::type;
}

// now the second argument can be deduced from the first
template<typename type_t, typename second_arg_t = details::deduced_second_arg_t<type_t>>
struct A
{
    second_arg_t some_value;
};

// or simplified, we use the deduced type internally
template<typename type_t>
struct B
{
    using some_value_t = details::deduced_second_arg_t<type_t>;
    some_value_t some_value;
};

struct X
{
};

int main()
{
    A<X> a_x;
    A<float> a_f;
    static_assert(std::is_same_v<decltype(a_x.some_value), int>);
    static_assert(std::is_same_v<decltype(a_f.some_value), float>);

    B<X> b_x;
    B<float> b_f;
    static_assert(std::is_same_v<decltype(b_x.some_value), int>);
    static_assert(std::is_same_v<decltype(b_f.some_value), float>);

    return 0;
}
2
463035818_is_not_an_ai On

If A<X,..> is the only special case you can use std::conditional_t:

#include <type_traits>

template <typename T1,typename T2>
class A_impl {};

struct X {};

template<typename T1>
using A = A_impl<T1,std::conditional_t< std::is_same_v<T1,X>, int,T1>>;


int main(){
    A<X> a;     // shall construct an instance of A<X,int>
    A<float> b; // shall construct an instance of A<float,float>
}

I tried to keep your A intact with 2 template arguments. Actuallly if possible, I would drop the second argument and instead use a member alias for T2 when T2 is always determined from T1:

#include <type_traits>

struct X {};

template<typename T1>
struct A {
    using T2 = std::conditional_t< std::is_same_v<T1,X>, int,T1>;
};


int main(){
    A<X> a;     // shall construct an instance of A<X,int>
    A<float> b; // shall construct an instance of A<float,float>
}

When A<X,...> is not the only special case you can still use std::conditional but it will get hairy quickly. For a general mapping from T1 to T2 I'd rather define a trait T2_from_T1_t that can be specialized accordingly:

template<typename T1>
struct A {
    using T2 = T2_from_T1_t<T1>;
};
1
kaisong On

Based on @463035818_is_not_an_ai 's accepted answer, I saw it can be cut even shorter by writing the conditional directly into the default template argument syntax:

struct X;

template<typename T1, typename T2 = std::is_same_v<X,T1>, int,T1> >
class A{
    // ...
};

int main(){
    A<X> a;     // shall construct an instance of A<X,int>
    A<float> b; // shall construct an instance of A<float,float>
}