Классная эквивалентность частичной специализации
Когда совпадают объявления двух отдельных классов шаблонов?
В приведенном ниже коде есть две частичные объявления специализации:
-
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
но подстановка все еще применяется, что не означает, что они эквивалентны.