Ответ 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.