0

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.

kaisong
  • 65
  • 6

3 Answers3

2

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>;
};
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • Thank you for your helpful solution! Can you explain your proposal with the "member alias"? X is in fact the only instance for which I need the conditional. – kaisong Aug 10 '23 at 13:57
  • @kaisong its just about moving the type I used for `T2` to an alias rather than pluggin it into `A_impl`. See edit – 463035818_is_not_an_ai Aug 10 '23 at 13:59
1

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;
}
Pepijn Kramer
  • 9,356
  • 2
  • 8
  • 19
  • A very beautiful answer as well. And very versatile when it comes to more complicated conditionals. Thank you – kaisong Aug 10 '23 at 14:02
0

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>
}
kaisong
  • 65
  • 6
  • 1
    the question is whether you want to enable eg `A`, ie something that is not `A` nor `A`. This answer allows it, mine does in the first version, but then I understood that you do not need it – 463035818_is_not_an_ai Aug 10 '23 at 14:02