Home > database >  Partial template specialization with auto template argument
Partial template specialization with auto template argument

Time:02-22

I have two enums

#include <iostream>

enum class E1 : unsigned int
{
    E11 = 1
};

enum class E2 : unsigned int
{
    E21 = 1
};

which have identical underlying values (1) in this case. Next, I have a class C which has two template parameters, an integer j and a value i with type auto.

template<int j, auto i>
struct C
{ C() { std::cout << "none\n"; } };

I want to partially specialize this class for both E1::E11 and E2::E21 like this:

template<int j>
struct C<j, E1::E11>
{ C() { std::cout << j << "E11\n"; } };

template<int j>
struct C<j, E2::E21>
{ C() { std::cout << j << "E21\n"; } };

And for completeness sake here is main that instantiates two objects:

int main()
{
    C<0,E1::E11> e11;
    C<1,E2::E21> e21;
    return 0;
}

The code above works absolutely fine on gcc and icc as can be verified on Godbolt (full code)

But it fails with any other compiler (clang, msvc). With the following message

<source>:27:18: error: ambiguous partial specializations of 'C<0, E1::E11>'
    C<0,E1::E11> e11;
                 ^
<source>:18:8: note: partial specialization matches [with j = 0]
struct C<j, E1::E11>
       ^
<source>:22:8: note: partial specialization matches [with j = 0]
struct C<j, E2::E21>

It's kind of clear to me why this happens. The question that I can't seem to answer though is whether it is possible to solve this in a standard compatible way (if this is some gcc or icc feature) or if there's a workaround for the failing compilers (clang, msvc).

Btw. if I remove the int j template argument and just leave the auto i the code compiles with all compilers.

Thanks in advance,

Arno

CodePudding user response:

The auto template argument can be replaced by

template<int j,typename T,T i>
struct C
{ C() { std::cout << "none\n"; } };

If you are fine with more typing you can explicitly specify the type of the enum:

#include <iostream>

enum class E1 : unsigned int
{
    E11 = 1
};

enum class E2 : unsigned int
{
    E21 = 1
};


template<int j,typename T,T i>
struct C
{ C() { std::cout << "none\n"; } };


template<int j>
struct C<j,E1, E1::E11>
{ C() { std::cout << j << "E11\n"; } };

template<int j>
struct C<j,E2, E2::E21>
{ C() { std::cout << j << "E21\n"; } };


int main()
{
    C<0,E1,E1::E11> e11;
    C<1,E2,E2::E21> e21;
    return 0;
}

And for less typing you can use a helper function:

template <int j,auto i>
auto make_C(){
    return C<j,decltype(i),i>();
}

int main()
{
    auto e11 = make_C<0,E1::E11>();
    auto e21 = make_C<1,E2::E21>();
}

... or a type trait:

template <int j,auto i>
struct C_helper {
    using type = C<j,decltype(i),i>;
};

int main()
{
    C_helper<0,E1::E11>::type e11;
    C_helper<1,E2::E21>::type e21;
}

CodePudding user response:

msvc/clang seems to have issue with partial specialization in your cases :(

As workaround, you might split the parameter in 2 classes:


template <auto i>
struct EC
{
    template <int j>
    struct C
    {
        C() { std::cout << "none\n"; }
    };
};

template <>
struct EC<E1::E11>
{
    template <int j>
    struct C
    {
        C() { std::cout << j << "E11\n"; }
    };
};

// Same for E2::E21

and then

EC<E1::E11>::C<0> e11;
EC<E2::E21>::C<0> e21;

Demo

  • Related