Compile time type determination in C++
A coworker recently showed me some code that he found online. It appears to allow compile time determination of whether a type has an "is a" relationship with another type. I think this is totally awesome, but I have to admit that I'm clueless as to how this actually works. Can anyone explain this to me?
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;
}
Once this function is defined, you can use it like this:
#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开发者_Go百科 is related to base" << std::endl;
if( !isRelated<base>( u ) )
std::cout << "u is not related to base" << std::endl;
}
It declares two overloaded functions named test
, one taking a Base
and one taking anything (...)
, and returning different types.
It then calls the function with a Derived
and checks the size of its return type to see which overload is called. (It actually calls the function with the return value of a function that returns Derived
, to avoid using memory)
Because enum
s are compile-time constants, all of this is done within the type system at compile-time. Since the functions don't end up getting called at runtime, it doesn't matter that they have no bodies.
I'm no C++ expert, but it looks to me like the point is to get the compiler to decide between the two overloads of test()
. If Derived
is derived from Base
then the first one will be used, which returns char
, otherwise the second one will be used - which returns char[2]
. The sizeof()
operator then determines which of these happened and sets the value of conversion::exists
accordingly.
It's pretty cool but it doesn't actually work, because user-defined conversion is preferred to ellipsis match, and const reference can bind the temporary result of a user-defined conversion. So char*p; is_related<bool>(p);
would return true
(tested in VS2010).
If you want to truly test for an inheritance relationship, a similar approach can be taken but using pointers instead of references.
Is there any reason you wouldn't use something like this instead:
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)) };
}
?
e.g.:
IsRelated<Base, Derived>::exists
That way you have access to the information at compile-time.
By the way, you can use __is_base_of
from "type_traits" introduced in std::tr1
(MSCV 2008 compiler has intrinsics support for that).
The original code will construct an object of Derived, it may brings unexpected result. Below work may be an alternative choice:
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) );
}
精彩评论