Метафункция IsPointer‹T›
Метафункция IsPointer‹T›
Задачу построения подобной метафункции решили в 2000 году сотрудники Adobe Systems Incorporated Мэт Маркус и Джесс Джонс. Суть решения сводится к использованию выражения вызова перегруженных функций внутри sizeof():
// Типы TrueType и FalseType могут быть определены произвольным образом,
// главное чтобы выполнялось условие: sizeof(TrueType)!= sizeof(FalseType).
struct TrueType {char dummy_ [1];};
struct FalseType {char dummy_ [100];};
// Промежуточный класс PointerShim нужен,
// чтобы избежать ошибочной работы метафункции
// IsPointer в случае параметризации классом, в котором определен
// оператор преобразования к указателю.
struct PointerShim {
PointerShim(const volatile void*);
};
// Т.к. функции ptr_discriminator на самом деле не вызываются, реализации не требуется.
TrueType ptr_discriminator(PointerShim);
FalseType ptr_discriminator(…);
// IsPointer‹T›::value == true, если T является указателем,
// IsPointer‹T›::value == false в противном случае.
template‹class T›
class IsPointer {
private:
static T t_;
public:
enum {
value = sizeof(ptr_discriminator(t_)) == sizeof(TrueType)};
};
// Так как объект типа void создан быть не может,
// случай IsPointer‹void› должен обрабатываться отдельно.
template‹›
class IsPointer‹void› {
public:
enum {value = false};
};
ПРЕДУПРЕЖДЕНИЕ Строго говоря, необходимо предоставлять не только специализацию для void, но и для соответствующих cv-квалифицированных разновидностей: const void, volatile void, const volatile 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.