Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Auto non-type template parameter: ambiguous partial specializations in Clang

Clang (7, 8, trunk) rejects the following code

enum class E {};
inline static constexpr auto e = E{};
// inline static constexpr auto e = nullptr;

template<auto, int> class S;
template<int a, int b> class S<a, b> {};
template<int b> class S<e, b> {};

int main() {
    S<0, 0> s;
}

with an error:

error: ambiguous partial specializations of 'S<0, 0>'
note: partial specialization matches [with a = 0, b = 0]
template<int a, int b> class S<a, b> {};
                             ^
note: partial specialization matches [with b = 0]
template<int b> class S<e, b> {};
                      ^
  1. Why is it ambiguous? How can e match 0? If I replace E{} with nullptr, Clang stops complaining. This looks like a Clang's bug. GCC compiles it just fine.

  2. If it is a bug, what is a workaround? In my case, the auto parameter can be either E (and only one value E{}) or int. Then:

    template<auto, int, typename> class S_impl;
    template<int a, int b> class S_impl<a, b, int> {};
    template<int b> class S_impl<e, b, E> {};
    
    template<auto a, int b> using S = S_impl<a, b, decltype(a)>;
    

    Is there a more succinct way?

like image 670
Evg Avatar asked May 14 '19 08:05

Evg


1 Answers

Clang is doing the deduction wrong. It is similar to this bug, linked to this question (not exactly identical as you are using auto in template parameters which will prevent you to compile using stdc++14).

An interesting case is that it is not the case if it's a complete specialization; only on partial specialization :

#include <iostream>

enum class E {};
inline static constexpr auto e = E{};

template <auto a, int b>
class FOO;
template <int a, int b > class FOO<a, b> {};
template <int b> class FOO<e, b> {};

template <auto a, int b>
class BAR;
template <int a, int b > class BAR<a, b> {};
template <> class BAR<e, 0> {};

template <auto a>
class BAZ;
template <int a> class BAZ<a> {};
template <> class BAZ<e> {};

int main() {
    // FOO <0, 0> foo; // <= Not Ok
    BAR<0, 0> bar; // <= Ok
    BAZ<0> baz; // <= Ok
}

Any solution that force the deduction of the type template parameter will work therefore your suggested solution is perfectly valid. IMHO, I would avoid using auto in template parameter when not necessary to improve readability :

template <typename T, T value, int> class S_impl; // <= this auto is not necessary
template <int a, int b> class S_impl<int, a, b> {};
template <int b> class S_impl<E, e, b> {};
// Either define S to use S<0,0> or directly use S_impl<int, 0, 0>
template <auto a, int b> using S = S_impl<decltype(a), a, b> // <= this auto is necessary
like image 143
Clonk Avatar answered Oct 17 '22 06:10

Clonk