public:
enum {value = false};
};
ПРЕДУПРЕЖДЕНИЕ Строго говоря, необходимо предоставлять не только специализацию для void, но и для соответствующих
ПРИМЕЧАНИЕ Функции, подобные ptr_discriminator, иногда называют дискриминирующими.
Техника основана на том, что во время компиляции выражения sizeof(ptr_discriminator(t_)) компилятор вынужден выбрать из двух перегруженных функций ptr_discriminator наиболее подходящую. В случае, если IsPointer‹T›::t_ является указателем, будет выбрана функция ptr_discriminator(PointerShim), возвращающая значение типа TrueType, и значение IsPointer‹T›::value обращается в true, т.к. sizeof(ptr_discriminator(PointerShim)) – sizeof(TrueType); в противном случае подходящей является функция ptr_discriminator(…)и значением IsPointer‹T›::value является false, т.к. sizeof(ptr_discriminator(…)) – sizeof(FalseType), а типы TrueType и FalseType выбраны таким образом, что sizeof(TrueType)!= sizeof(FalseType).
Класс PointerShim необходим для того, чтобы классы, имеющие операцию приведения к указателю, не считались указателями. На первый взгляд может показаться, что можно «упростить» дискриминирующие функции ptr_discriminator, избавившись от промежуточного класса PointerShim:
TrueType simple_ptr_discriminator(const volatile void*);
FalseType simple_ptr_discriminator(…);
Однако, в этом случае, метафункция IsPointer будет работать неверно, например, для таких классов:
struct C {
operator int*() const {return 0;}
};
Так как класс C имеет операцию приведения к указателю, функция simple_ptr_discriminator может быть вызвана с любым объектом этого класса, и, следовательно, метафункция, построенная с использованием simple_ptr_discriminator, будет ошибочно определять подобные классы как указатели.
Пример. Для пущей ясности можно рассмотреть, как работает метафункция IsPointer‹T› на примере типа int. IsPointer‹int› разворачивается компилятором примерно в следующее:
// псевдокод
class IsPointer‹int› {
private:
static int t_;
public:
enum {value = sizeof(ptr_discriminator(t_)) == sizeof(TrueType)};
};
ptr_discriminator(PointerShim) для t_ не подходит, т.к. объект PointerShim может быть создан только из указателя. Следовательно, подходящей будет оставшаяся ptr_discriminator(…), которая возвращает FalseType. Значит, в данном случае выражение sizeof(ptr_discriminator(t_)) эквивалентно выражению sizeof(FalseType), значение которого по условию не равно sizeof(TrueType). Следовательно, IsPointer‹int›::value == false.
Симуляция частичной специализации по виду аргумента шаблона
Использовать полученную метафункцию IsPointer‹T› для симуляции частичной специализации по виду аргумента шаблона можно примерно следующим образом:
// Реализация общего случая: T не является указателем.
template‹class T›
class C_ {
//…
};
// Реализация случая, когда T является указателем.
template‹class T›
class C_ptr_ {
//…
};
// Traits для случая, когда T является указателем
template‹bool T_is_ptr›
struct CTraits {
template‹class T›
struct Args {
typedef C_ptr_‹T› Base;
};
};
// Traits для случая, когда T не является указателем.
template‹›
struct CTraits‹false› {
template‹class T› struct Args {
typedef C_‹T› Base;
};
};
// Класс, предназначенный для использования клиентами.
template‹class T›
class C: public CTraits‹IsPointer‹T›::value›::template Args‹T›::Base {
//…
};
Ограничения
Приведенная техника симуляции частичной специализации обладает некоторыми ограничениями по сравнению с «настоящей» частичной специализацией шаблонов классов.
Одним из наиболее заметных ограничений является то, что дискриминирующие функции, применяющиеся при создании многих метафункций, требуют объявления переменной, поэтому не работают с абстрактными классами. Например, в случае с IsPointer‹T› объявляется статическая переменная t_. Несмотря на то, что ее определение не требуется, специализация шаблона IsPointer‹T› абстрактным классом приведет к ошибке компиляции. По этой же причине приходится предоставлять специализации шаблонов метафункций для void.
Другим ограничением является то, что некоторые метафункции, построенные с использованием дискриминирующих функций, например, IsConst‹T›, IsVolatile‹T›, IsReference‹T› и т.п., некорректно работают в случае, если T имеет квалификаторы и const и volatile одновременно (например, const volatile int&). Существующая реализация метафункций IsConst‹T› и IsVolatile‹T› без «настоящей» частичной специализации сводится к использованию соответствующих дискриминирующих функций:
TrueType const_discriminator(const volatile void*);
FalseType const_discriminator(volatile void*);
template‹class T›
struct IsConst {
private:
static T t_;
public:
enum {value = sizeof(const_discriminator(&t_)) == sizeof(TrueType)};
};
template‹›
class IsConst‹void› {
public:
enum {value = false};
};
TrueType volatile_discriminator(const volatile void*);
FalseType volatile_discriminator(const void*);
template‹class T›
struct IsVolatile {
private:
static T t_;
public
:
enum {value = sizeof(volatile_discriminator(&t_)) – sizeof(TrueType)};
};
template‹›
class IsVolatile‹void› {
public:
enum {value = false};
};
Очевидно, что эти метафункции не работают, если в качестве аргумента им передан тип имеющий как const, так и volatile квалификацию. Реализация IsReference‹T› основывается на том факте, что добавление
template‹class T›
class IsReference {