Классная эквивалентность частичной специализации

Когда совпадают объявления двух отдельных классов шаблонов?

В приведенном ниже коде есть две частичные объявления специализации:

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

constrain - это шаблон псевдонима, равный T но ограниченный с помощью enable_if со вторым параметром в качестве концепции.

GCC считает, что эти две частичные специализации различны, но Clang и MSVC считают, что они эквивалентны и, таким образом, отвергают код:

#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)

Я не могу найти ничего в стандарте, который бы указывал на эквивалентность частичной специализации. [temp.type], похоже, не применяется здесь.

Что говорит стандарт об эквивалентности декларации частичной специализации?

Ответ 1

Это CWG 1980, "Эквивалентные, но не функционально эквивалентные переоценки":

В примере, подобном

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();

представляется, что второе объявление f является повторной записью первого, но различимым с помощью SFINAE, т.е. эквивалентным, но не функционально эквивалентным.

Примечания к ноябрю 2014 года:

По мнению CWG, эти две декларации не должны быть эквивалентными.

Это все еще актуальная проблема. поведение gcc больше соответствует желанию, чтобы они были разными. [temp.alias]/2 и [temp.alias]/3 - соответствующие правила прозрачности:

Когда идентификатор шаблона ссылается на специализацию шаблона псевдонима, он эквивалентен связанному типу, полученному путем подстановки его шаблонных аргументов для параметров шаблона в идентификаторе типа шаблона псевдонимов.

Однако, если идентификатор шаблона зависит, последующая замена аргумента шаблона по-прежнему применяется к идентификатору шаблона.

которые находятся в конфликте здесь. В упрощенном примере из проблемы X<T, U> эквивалентно T - это означает, что обе декларации имеют только возвращаемый тип void но подстановка все еще применяется, что не означает, что они эквивалентны.