Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Class template partial specialization equivalence

When do two different class template partial specialization declarations match?

In the code below there are two partial specialization declarations:

  • S<constrain<T,has_accept_>, void>
  • S<constrain<T,has_visit_>, void>

constrain is an alias template that equals to T but is constrained using an enable_if trick with the second parameter as a concept.

GCC considers that these two partial specialization are different but Clang and MSVC consider that they are equivalent and thus reject the code:

#include <type_traits>
#include <utility>
using namespace std;

template<class T,class=void>
struct has_accept
  :false_type{};
template<class T>
struct has_accept<T,void_t<decltype(declval<const T&>().accept())>>
  :true_type{};

template<class T,class=void>
struct has_visit
  :false_type{};
template<class T>
struct has_visit<T,void_t<decltype(declval<const T&>().visit())>>
  :true_type{};

//pre c++17 clang/MSVC fix: default argument of template 
//   used as template template argument not implemented yet
template<class T> using has_accept_ = has_accept<T>;
template<class T> using has_visit_ = has_visit<T>;

template<class T,template<class> class TT,class=enable_if_t<TT<T>::value>>
using constrain = T;

template<class T,class=void>
struct S
  :false_type{};
template<class T>
struct S<constrain<T,has_accept_>,void>  // (1)
  :true_type{};
template<class T>
struct S<constrain<T,has_visit_>,void>  // (2)
 :true_type{};  // ==> MSVC and Clang: error (2) redefines (1)

I cannot find anything in the standard that would specify partial specialization equivalence. [temp.type] does not seem to apply here.

What does the standard say about partial specialization declaration equivalence?

like image 308
Oliv Avatar asked Sep 10 '18 09:09

Oliv


1 Answers

This is CWG 1980, "Equivalent but not functionally-equivalent redeclarations":

In an example like

template<typename T, typename U> using X = T;
template<typename T> X<void, typename T::type> f();
template<typename T> X<void, typename T::other> f();

it appears that the second declaration of f is a redeclaration of the first but distinguishable by SFINAE, i.e., equivalent but not functionally equivalent.

Notes from the November, 2014 meeting:

CWG felt that these two declarations should not be equivalent.

This is still an active issue. gcc's behavior is more in line with the desire that these be different. [temp.alias]/2 and [temp.alias]/3 are the relevant transparency rules:

When a template-id refers to the specialization of an alias template, it is equivalent to the associated type obtained by substitution of its template-arguments for the template-parameters in the type-id of the alias template.

However, if the template-id is dependent, subsequent template argument substitution still applies to the template-id.

which are in conflict here. In the simplified example from the issue, X<T, U> is equivalent to T - which means both declarations just have return type of void - but substitution still applies, which doesn't exactly mean they're equivalent.

like image 102
Barry Avatar answered Nov 12 '22 20:11

Barry