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

Использование 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 не используется в частичной специализации.)

4b9b3361

Ответ 1

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

Решение основывается на функции шаблонов псевдонимов, которая была указана только недавно, в стандартных рабочих черновиках после окончательной версии С++ 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

-end пример]

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

#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] ([p.8.3] может исчезнуть в будущем, поскольку он избыточен - CWG 2033):

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

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

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


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

  • Clang 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 С++ 2015 с /W4 работает, как описано выше. Старшим версиям не нравится decltype в аргументе по умолчанию, но сам метод все еще работает - замена аргумента по умолчанию другим способом выражения ограничения заставляет его работать с обновлением версии 2013.

Ответ 2

IF исходное объявление 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. Я все еще жду ответа. Это может быть тот, который окончательно демонстрирует, что никакое другое решение не возможно.

Ответ 3

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

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

Смотрите также: Когда использовать static_assert вместо SFINAE?. static_assert является конструкцией С++ 11, однако для компиляторов pre-С++ 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 не указывается явно, то статические утверждения можно опустить.