Подтвердить что ты не робот

Результаты странной специализации вложенных классов в отношении как gcc, так и clang

При написании небольшой библиотеки метапрограммирования шаблонов для личного использования я столкнулся с интересной проблемой.

Так как я повторно использовал несколько частных специализаций для некоторых метафайлов, я решил, что поместил бы их в общий шаблонный класс и использовал теги вместе с вложенной частичной специализацией, чтобы обеспечить различия в поведении.

Проблема в том, что я получаю бессмысленные (мне) результаты. Вот минимальный пример, демонстрирующий то, что я пытаюсь сделать:

#include <iostream>
#include <cxxabi.h>
#include <typeinfo>

template <typename T>
const char * type_name()
{
    return abi::__cxa_demangle(typeid(T).name(), nullptr, nullptr, nullptr);
}

template <typename... Args>
struct vargs {};

namespace details   
{
    template <typename K>
    struct outer
    {
        template <typename Arg>
        struct inner
        {
            using result = Arg;
        };
    };
}

struct tag {};

namespace details
{
    template <>
    template <typename Arg, typename... Args>
    struct outer<tag>::inner<vargs<Arg, Args...>>
    {
        using result = typename outer<tag>::inner<Arg>::result;
    };
}

template <typename T>
using test_t = typename details::outer<tag>::inner<T>::result;

int main()
{
    using t = test_t<vargs<char, int>>;
    std::cout << type_name<t>() << '\n';
    return 0;
}

Я получаю vargs<char, int> в качестве вывода при использовании версии gmp и tag версии 5.1.0 при использовании версии clang 3.6.0. Мое намерение состояло в том, чтобы вышеприведенный фрагмент кода напечатал char, поэтому я довольно озадачен этими результатами.

Является ли вышеуказанная часть кода законной или она демонстрирует поведение undefined? Если это законно, каково ожидаемое поведение в соответствии со стандартом?

4b9b3361

Ответ 1

Ваш код верен; внеклассный неявный экземпляр класса шаблонов классов классов, частичные специализации предназначены для разрешения Стандартом, если они определены достаточно рано.

Сначала попробуйте минимальный пример - отметив, кстати, что здесь ничего нет, что требует С++ 11:

template<class T> struct A {
  template<class T2> struct B { };
};
// implicitly instantiated class template member class template partial specialization
template<> template<class T2>
  struct A<short>::B<T2*> { };
A<short>::B<int*> absip;    // uses partial specialization?

Как отмечалось в других разделах, MSVC и ICC используют частичную специализацию, как ожидалось; clang выбирает частичную специализацию, но смешивает свои параметры типа, сглаживая T2 до short вместо int; и gcc полностью игнорирует частичную специализацию.

Почему нестандартная неявно созданная частичная специализация шаблона шаблона класса шаблона допускается

Проще говоря, ни один из языков, разрешающих другие формы классов шаблонов шаблонов классов классов, исключает внеклассную неявно создаваемую экземплярную структуру шаблонов классов классов классов. В [temp.mem] мы имеем:

1 - шаблон может быть объявлен в шаблоне класса или класса; такой шаблон называется шаблоном-членом. шаблон члена может быть определен внутри или вне его определения класса или определения шаблона класса. [...]

Частичная специализация шаблона шаблона - это объявление шаблона ( [temp.class.spec]/1). В этом же параграфе есть пример неклассифицированной частичной специализации шаблона класса шаблона класса ( [temp.class.spec]/5):

template<class T> struct A {
  struct C {
    template<class T2> struct B { };
  };
};
// partial specialization of A<T>::C::B<T2>
template<class T> template<class T2>
  struct A<T>::C::B<T2*> { };
A<short>::C::B<int*> absip; // uses partial specialization

Здесь нет ничего, чтобы указать, что охватывающая область не может быть неявной специализацией шаблона вмещающего класса.

Аналогично, есть примеры частичной специализации шаблона класса класса класса класса и неявно созданного экземпляра класса шаблона шаблона класса шаблона полной специализации ( [temp.class.spec.mfunc]/2):

template<class T> struct A {
  template<class T2> struct B {}; // #1
  template<class T2> struct B<T2*> {}; // #2
};
template<> template<class T2> struct A<short>::B {}; // #3
A<char>::B<int*> abcip; // uses #2
A<short>::B<int*> absip; // uses #3
A<char>::B<int> abci; // uses #1

(clang (начиная с 3.7.0-svn235195) получает второй пример неправильно, он выбирает # 2 вместо # 3 для absip.)

В то время как это явно не упоминает неявно созданный экземпляр частичной специализации шаблона класса шаблона класса шаблона, он также не исключает его; причина, по которой это не здесь, заключается в том, что она не имеет значения для конкретной точки, которая связана с тем, какие первичные шаблоны или частичные специализированные шаблоны рассматриваются для конкретной специализации.

Per [temp.class.spec]:

6 - [...], когда первичный используется имя шаблона, любые ранее объявленные частичные специализации первичного шаблона также считается.

В приведенном выше минимальном примере A<short>::B<T2*> является частичной специализацией первичного шаблона A<short>::B и поэтому следует учитывать.

Почему это не может быть разрешено

В другом обсуждении мы видели упоминание о том, что неявное инстанцирование (вмещающего шаблона класса) может привести к неявному экземпляру определения основной специализации шаблона, что приведет к плохо сформированному программному NDR, т.е. UB; [templ.expl.spec]:

6 - Если шаблон, шаблон-член или член шаблона класса явно специализирован, то эта специализация должны быть объявлены до первого использования этой специализации, которая приведет к неявному экземпляру иметь место, в каждой единицы перевода, в которой такое использование происходит; диагностика не требуется. [...]

Однако здесь шаблон шаблона элемента шаблона класса не используется до его создания.

Что думают другие люди

В DR1755 (активная) приведенный пример:

template<typename A> struct X { template<typename B> struct Y; };
template struct X<int>;
template<typename A> template<typename B> struct X<A>::Y<B*> { int n; };
int k = X<int>::Y<int*>().n;

Это считается проблематичным только с точки зрения существования второй строки, инстанцирующей охватывающий класс. Не было никаких предложений от заявителя (Ричарда Смита) или из CWG, что это может быть недействительным даже в отсутствие второй линии.

В n4090 приведенный пример:

template<class T> struct A {
  template<class U> struct B {int i; }; // #0
  template<> struct B<float**> {int i2; }; // #1
  // ...
};
// ...
template<> template<class U> // #6
struct A<char>::B<U*>{ int m; };
// ...
int a2 = A<char>::B<float**>{}.m; // Use #6 Not #1

Здесь поднятый вопрос имеет приоритет между полной спецификацией шаблона класса шаблона класса класса класса и неполной специализацией шаблона класса шаблона класса вне класса; нет никаких предположений о том, что #6 вообще не рассматривается.