использование SFINAE для специализации класса шаблона

Предположим, у меня есть эти заявления

template<typename T> class User;
template<typename T> class Data;

и хочу реализовать User<> за T = Data<some_type> и любой класс, полученный из Data<some_type> но также учитывают другие специализации, определенные в другом месте.

Если у меня еще не было объявления шаблона класса User<>Я мог бы просто

template<typename T,
typename A= typename std::enable_if<is_Data<T>::value>::type>
class User { /*...*/ };

где

template<template<typename> data>> struct is_Data
{ static const bool value = /* some magic here (not the question) */; };

Тем не менее, он имеет два параметра шаблона и, следовательно, конфликтует с предыдущим объявлением, где User<> объявляется только с одним параметром шаблона. Есть ли что-нибудь еще, что я могу сделать?

(Заметка

template<typename T,
typename A= typename std::enable_if<is_Data<T>::value>::type>
class User<T> { /*...*/ };

не работает (аргументы шаблона по умолчанию не могут использоваться в частичных специализациях),
и не делает

template<typename T> class User<Data<T>> { /*...*/ };

так как он не допускает типы, полученные из Data<>также

template<typename T>
class User<typename std::enable_if<is_Data<T>::value,T>::type>
{ /*...*/ };

поскольку параметр шаблона T не используется в частичной специализации.)

33

Решение

Поскольку ты сказал, что все еще ждешь лучшего ответа, вот мое мнение. Это не идеально, но я думаю, что это поможет вам, насколько это возможно, используя SFINAE и частичные специализации. (Я думаю, что Concepts предоставит полное и элегантное решение, но нам придется подождать немного дольше.)

Решение опирается на особенность шаблонов псевдонимов, которая была указана только недавно, в стандартных рабочих проектах после окончательной версии C ++ 14, но некоторое время поддерживалась реализациями. Соответствующая формулировка в проекте N4527 [14.5.7p3]:

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

template<typename...> using void_t = void;
template<typename T> void_t<typename T::foo> f();
f<int>(); // error, int does not have a nested type foo

— конец примера]

Вот полный пример реализации этой идеи:

#include <iostream>
#include <type_traits>
#include <utility>

template<typename> struct User { static void f() { std::cout << "primary\n"; } };

template<typename> struct Data { };
template<typename T, typename U> struct Derived1 : Data<T*> { };
template<typename> struct Derived2 : Data<double> { };
struct DD : Data<int> { };

template<typename T> void take_data(Data<T>&&);

template<typename T, typename = decltype(take_data(std::declval<T>()))>
using enable_if_data = T;

template<template<typename...> class TT, typename... Ts>
struct User<enable_if_data<TT<Ts...>>>
{
static void f() { std::cout << "partial specialization for Data\n"; }
};

template<typename> struct Other { };
template<typename T> struct User<Other<T>>
{
static void f() { std::cout << "partial specialization for Other\n"; }
};

int main()
{
User<int>::f();
User<Data<int>>::f();
User<Derived1<int, long>>::f();
User<Derived2<char>>::f();
User<DD>::f();
User<Other<int>>::f();
}

Запуск это печатает:

primary
partial specialization for Data
partial specialization for Data
partial specialization for Data
primary
partial specialization for Other

Как видите, есть морщина: частичная специализация не выбрана для DDи это не может быть из-за того, как мы это объявили. Итак, почему бы нам просто не сказать

template<typename T> struct User<enable_if_data<T>>

и позволить ему соответствовать DD также? Это на самом деле работает в GCC, но Clang и MSVC правильно отклоняют его из-за [14.5.5p8.3, 8.4] ([p8.3] может исчезнуть в будущем, так как это избыточно — CWG 2033):

  • Список аргументов специализации не должен совпадать с
    список неявных аргументов основного шаблона.
  • Специализация должна быть более специализированной, чем основной шаблон (14.5.5.2).

User<enable_if_data<T>> эквивалентно User<T> (Подстановка по модулю в этот аргумент по умолчанию, который обрабатывается отдельно, как объяснено в первой цитате выше), таким образом, недопустимая форма частичной специализации. К сожалению, такие вещи, как DD потребует, как правило, аргумент частичной специализации в виде T — нет другой формы, которая может иметь и все еще соответствовать каждому случаю. Поэтому я боюсь, что мы можем окончательно сказать, что эта часть не может быть решена в рамках данных ограничений. (Там же Основная проблема 1980, который намекает на некоторые возможные будущие правила, касающиеся использования псевдонимов шаблонов, но я сомневаюсь, что они сделают наш случай действительным.)

Пока классы, полученные из Data<T> сами являются шаблонными специализациями, дальнейшее ограничение их с помощью описанной выше техники будет работать, так что, надеюсь, это будет вам полезно.


Поддержка компилятора (это то, что я тестировал, другие версии также могут работать):

  • Лязг 3.3 — 3.6.0, с -Wall -Wextra -std=c++11 -pedantic — работает как описано выше.
  • GCC 4.7.3 — 4.9.2, те же опции — те же, что и выше. Любопытно, что GCC 5.1.0 — 5.2.0 больше не выбирает частичную специализацию, используя правильную версию кода. Это похоже на регрессию. У меня нет времени, чтобы составить правильный отчет об ошибке; не стесняйтесь делать это, если хотите. Кажется, проблема связана с использованием пакетов параметров вместе с параметром шаблона шаблона. В любом случае, GCC принимает неверную версию, используя enable_if_data<T>, так что это может быть временным решением.
  • MSVC: Visual C ++ 2015, с /W4работает, как описано выше. Старые версии не любят decltype в аргументе по умолчанию, но сам метод все еще работает — замена аргумента по умолчанию другим способом выражения ограничения заставляет его работать в обновлении 4 2013 года.
9

Другие решения

ЕСЛИ оригинальная декларация User<> может быть адаптирован к

template<typename, typename=std::true_type> class User;

тогда мы можем найти решение (следуя комментарию Люка Дантона, вместо того, чтобы использовать std::enable_if)

template<typename>
struct is_Data : std::false_type {};
template<typename T>
struct is_Data<Data<T>> : std::true_type {};

template<typename T>
class User<T, typename is_Data<T>::type >
{ /* ... */ };

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

19

Поскольку вы хотите реализовать его только при выполнении одного условия, простейшим решением будет использование статического утверждения. Он не требует SFINAE, выдает явную ошибку компиляции при неправильном использовании и объявление User<> не нуждается в адаптации:

template<typename T> class User {
static_assert(is_Data<T>::value, "T is not (a subclass of) Data<>");
/** Implementation. **/
};

Смотрите также: Когда использовать static_assert вместо сфина?. static_assert является конструкцией c ++ 11, однако для компиляторов до c ++ 11 доступно множество обходных путей, например:

#define STATIC_ASSERT(consdition,name) \
typedef char[(condition)?1:-1] STATIC_ASSERT_ ## name

Если декларация user<> может быть изменен, и вы хотите две реализации в зависимости от значения is_Dataто есть решение, которое не использует SFINAE:

template<typename T, bool D=is_Data<T>::value> class User;

template<typename T> class User<T, true> {
static_assert(is_Data<T>::value, "T is not (a subclass of) Data<>"); // Optional
/* Data implementation */
};

template<typename T> class User<T, false> {
static_assert(!is_Data<T>::value, "T is (a subclass of) Data<>"); // Optional
/* Non-data implementation */
};

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

5
По вопросам рекламы [email protected]