Search code examples
c++templateslanguage-lawyerpartial-specializationnon-type-template-parameter

Class template partial specialization for const and not-const pointers


I am trying to make a class template with non-type template parameter of pointer type, which has two specializations for const and not-const pointers.

This is my best attempt, accepted by Clang and GCC:

template <const int*>
struct A {};

template <int* p>
struct A<p>;

int v;
A<&v> a;

but A<&v> still uses the primary template with const pointer despite &v has type int*.

On the other hand, MSVC prints:

error C2753: 'A<p>': partial specialization cannot match argument list for primary template

Online demo: https://godbolt.org/z/EzoYoxEza

Is my partial specialization correct? And if yes, how can I use A<int*>?


Another attempt was to declare the primary template with auto non-type template parameter and specialize it twice:

template <auto>
struct A;

template <int* p>
struct A<p> {};

template <const int* p>
struct A<p>;

int v;
A<&v> a;

It works fine with GCC, but both Clang and MSVC complain:

Clang error: ambiguous partial specializations of 'A<&v>'
MSVC error C2752: 'A<&v>': more than one partial specialization matches the template argument list

Online demo: https://godbolt.org/z/43bj6EWq6

Considering that all compilers agreed in the previous example that template <int* p> is more specialized than template <const int* p>, which one is correct here?


Solution

  • As already mentioned, this is an MSVC bug. In C++20, there is a relatively elegant workaround:

    template <auto>
    struct A;
    
    template <auto p>
      requires std::same_as<decltype(p), const int*>
    struct A<p> { };
    
    template <auto p>
      requires std::same_as<decltype(p), int*>
    struct A<p> { };
    

    MSVC does correctly select the const int* and int* specializations in this case; see live example at Compiler Explorer.

    Note that this solution currently does not work with GCC due to a memoization bug (Bug 113274), so you may have to resort to something else entirely.

    You could also use #ifdef __GNUC__ to use your original version for anything but MSVC, and use this version just for MSVC. This solution would be absolutely disgusting and insane, but seems to be the only way to keep the template parameter list clean for all compilers.