Определение типа времени компиляции в С++
Недавно коллега показал мне код, который он нашел в Интернете. По-видимому, это позволяет определить время компиляции, имеет ли тип отношение "есть" к другому типу. Я думаю, что это совершенно потрясающе, но я должен признать, что я не знаю, как это работает. Может кто-нибудь объяснить это мне?
template<typename BaseT, typename DerivedT>
inline bool isRelated(const DerivedT&)
{
DerivedT derived();
char test(const BaseT&); // sizeof(test()) == sizeof(char)
char (&test(...))[2]; // sizeof(test()) == sizeof(char[2])
struct conversion
{
enum { exists = (sizeof(test(derived())) == sizeof(char)) };
};
return conversion::exists;
}
Как только эта функция определена, вы можете использовать ее следующим образом:
#include <iostream>
class base {};
class derived : public base {};
class unrelated {};
int main()
{
base b;
derived d;
unrelated u;
if( isRelated<base>( b ) )
std::cout << "b is related to base" << std::endl;
if( isRelated<base>( d ) )
std::cout << "d is related to base" << std::endl;
if( !isRelated<base>( u ) )
std::cout << "u is not related to base" << std::endl;
}
Ответы
Ответ 1
Он объявляет две перегруженные функции с именем test
, один принимает Base
и принимает что-либо (...)
и возвращает разные типы.
Затем он вызывает функцию с Derived
и проверяет размер возвращаемого типа, чтобы увидеть, какая перегрузка вызывается. (Он фактически вызывает функцию с возвращаемым значением функции, которая возвращает Derived
, чтобы избежать использования памяти)
Поскольку enum
- константы времени компиляции, все это выполняется в системе типов во время компиляции. Поскольку функции не получают вызов во время выполнения, не имеет значения, что у них нет тел.
Ответ 2
Я не эксперт на С++, но мне кажется, что нужно, чтобы компилятор решил между двумя перегрузками test()
. Если Derived
выводится из Base
, тогда будет использоваться первый, который возвращает char
, в противном случае будет использоваться второй, который возвращает char[2]
. Оператор sizeof()
затем определяет, какое из них произошло, и соответственно устанавливает значение conversion::exists
.
Ответ 3
Это довольно круто, но на самом деле это не работает, потому что пользовательское преобразование предпочтительнее совпадения с эллипсисом, а ссылка const может связывать временный результат пользовательского преобразования. Поэтому char*p; is_related<bool>(p);
вернет true
(проверен на VS2010).
Если вы хотите по-настоящему проверить отношения наследования, можно использовать аналогичный подход, но с помощью указателей вместо ссылок.
Ответ 4
Есть ли какая-то причина, по которой вы не использовали бы что-то вроде этого:
template<typename BaseT, typename DerivedT>
struct IsRelated
{
static DerivedT derived();
static char test(const BaseT&); // sizeof(test()) == sizeof(char)
static char (&test(...))[2]; // sizeof(test()) == sizeof(char[2])
enum { exists = (sizeof(test(derived())) == sizeof(char)) };
}
?
например:.
IsRelated<Base, Derived>::exists
Таким образом, вы получаете доступ к информации во время компиляции.
Ответ 5
Кстати, вы можете использовать __is_base_of
из "type_traits", представленного в std::tr1
(у компилятора MSCV 2008 есть поддержка intrinsics).
Ответ 6
Исходный код построит объект Derived, это может привести к неожиданному результату. Ниже работы может быть альтернативный вариант:
template<typename BaseT, typename CheckT>
inline bool isDerived(const CheckT &t){
char test(const BaseT *t);
char (&test(...))[2];
return (sizeof(test(&t)) == sizeof(char) );
}