'Partial template specialization with auto template argument

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



Solution 1:[1]

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

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Jarod42