开发者

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 enums 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) );
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜