#c #templates #specialization
#c #шаблоны #специализация
Вопрос:
Я играю с шаблонами и частичной специализацией, но есть одна специализация, которую я не знаю, как написать… Я упрощу код, чтобы его было легче читать.
Давайте подготовим
template <typename T>
class x
{
...
};
Обычно я могу специализироваться следующим образом :
class x<a_type>
{
...
};
Также работает с типами шаблонов :
template <typename T>
class x<std::vector<T>>
{
...
}
Теперь я хотел бы создать специализацию для типа, вложенного в шаблонный класс:
template <typename T>
class y
{
struct nested_type
{
y a_member;
};
...
};
// Here comes the specialization
template <typename T>
class x<y<T>::nested_type>
{
...
};
Это не удается. Я также пытался поместить ‘typename’ перед y::nested_type, но это не решило проблему. Ошибка компилятора:
type/value mismatch at argument 1 in template parameter list for ‘template <class T> struct x’
То, что я хочу сделать, кажется логичным, но я не уверен, возможно ли это. Я использую C 0x с g -4.5. Кто-нибудь знает правильный синтаксис для написания такой специализации?
Комментарии:
1. Я думаю, вам нужно отредактировать вопрос. Это должно быть
class x<....>
, а неclass<....>
в первых двух блоках. Вы уверены, что все аккуратно ввели?2. Да, вы правы. Это всего лишь небольшая ошибка в моем сообщении, поскольку я не копировал свой код, но предпочел использовать
x
ory
для упрощения проблемы. Я пробовал меньшие примеры, но ответ Майкла кажется хорошим — я не смог заставить работать маленький пример, и, поверьте мне, я много чего перепробовал!3. Обратите внимание, что вы получите более разумное сообщение об ошибке от GCC, если вы используете «правильный» синтаксис,
template<typename T> class x<typename y<T>::nested_type>
а неtemplate<typename T> class x<y<T>::nested_type>
(добавленоtypename
).
Ответ №1:
Ответ заключается в том, что вы не можете выполнить эту специализацию. Это не синтаксическая ошибка, а просто то, что не может быть реализовано. Вы должны видеть, что специализации шаблонов немного похожи на перегрузку функций. Компилятор должен принять аргумент type на сайте использования, просмотреть доступные специализации, найти соответствия и выбрать наилучшую (наиболее специализированную). Проблема с вашим примером заключается в том, что шаг «найти соответствие» не может быть реализован с такой специализацией. Компилятор может ожидать, что «nested_type» будет любым, не обязательно уникальным типом (как в вашем примере), это также может быть, например, вложенный typedef. Более того, компилятор не может предсказать, что он уже видит все специализации шаблона «y», поэтому, даже если nested_type является уникальным типом, вложенным в y (общий шаблон), это может быть вложенный typedef в предстоящем объявлении специализации шаблона для шаблона «y».
Точно так же, как при перегрузке функции и используемом там алгоритме сопоставления, компилятор ограничен в своих возможностях по определению типа, и что ограничивает его, так это количество допущений, которые он может сделать. Если у вас есть специализация для x<int>
и последующего использования x<int>
, совпадение тривиально, никаких выводов не требуется, никаких предположений не требуется. Если у вас есть специализация, подобная x<T*>
и более позднему использованию x<int*>
, сопоставление несложно, T может быть выведено как int
. Если у вас есть специализация типа x< y<T>::type >
, а затем вы используете любую версию x, как компилятор должен выводить T из y::type? Пришлось бы заменить T в y всеми возможными типами, которые существуют во всем мире, чтобы увидеть, есть ли тот, который приводит к соответствующему вложенному типу. Это необоснованное ожидание, и именно поэтому возможности шаблонов C для вывода типов на этом заканчиваются. Очень часто, чтобы узнать, следует ли ожидать, что компилятор сможет что-то разрешить, просто поставьте себя на его место и посмотрите, возможно ли это хотя бы отдаленно (ответ обычно ясен).
Комментарии:
1. Ну, я подумал, что если я использую,
x<y<int>::type>
например, компилятор мог бы вывестиy<int>::type
, а затем найти правильную специализациюx
. То, что вы говорите, действительно имеет смысл, я понимаю, что компилятор не может вывестиT
изx<y<T>::type>
, однако, в моем случае,T
указано так, чтоy<T>::type
может быть выведено, что позволяет разрешить специализацию. В качестве обходного пути я объявлю свою вложенную структуру вне класса. Спасибо за ваш ответ.2. Да, конечно, проблема заключается не в предполагаемом варианте использования (например,
x< y<int>::type >
), а в любых других вариантах использования, которые должны быть проверены на соответствие этой специализации, чтобы определить, может ли она быть сопоставлена с ней, и проверка этого соответствия невозможна либо из-за необходимости рассматривать бесконечное количество случаев, либо из-за необходимости делать необоснованные предположения (предполагая, чтоy<T>::type
никогда не является typedef для типа, «не связанного» с y<T>).
Ответ №2:
Вы действительно не можете выполнить эту частичную специализацию, но есть обходной путь, который вы можете использовать для достижения желаемого эффекта.
Вместо специализации заставьте целевой тип реализовать то, что вам нужно.
Вот минимальный пример для этого:
#include <vector>
#include <iostream>
#include <cassert>
// Default template defines an interface against the target type.
template <class T> struct Traits
{
using TraitsType = typename T::foo_type;
static void foo()
{
T::foo();
}
};
// This is the sample class.
template <class T> struct MyStuff
{
struct NestedType
{
int x;
// It implements the desired features.
using foo_type = int;
static void foo()
{
std::cout << "Using Nested version!n";
}
};
};
// For built in types you can use specialization.
template <> struct Traits<int>
{
using TraitsType = double;
static void foo()
{
std::cout << "Using int version.n";
}
};
//... If you can't touch the nested type, the you are SOL.
int main()
{
static_assert(std::is_same<Traits<int>::TraitsType, double>::value);
static_assert(std::is_same<Traits<MyStuff<int>::NestedType>::TraitsType, int>::value);
static_assert(std::is_same<Traits<MyStuff<double>::NestedType>::TraitsType, int>::value);
Traits<int>::foo(); // Prints "Using int version"
Traits<MyStuff<int>::NestedType>::foo(); // Prints "Using Nested version!n"
Traits<MyStuff<double>::NestedType>::foo(); // Prints "Using Nested version!n"
return 0;
}