views:

339

answers:

6

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 is related to base" << std::endl;

    if( !isRelated<base>( u ) )
        std::cout << "u is not related to base" << std::endl;
} 
+4  A: 

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.

Evgeny
true..its only based on overload resolution.
hype
+6  A: 

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.

SLaks
`derived()` isn't there for saving memory - the `sizeof()` expression is evaluated at compile time anyway - it is one way of not requiring `Derived` to be default constructible (i.e. to avoid using `test(Derived())`).
Georg Fritzsche
@gf: I meant as opposed to declaring a pointer to a `Dervied`.
SLaks
+3  A: 

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.

Ben Voigt
+1  A: 

You can also follow the link below from Stroustrup's homepage. Why can't I define constraints for my template parameters?

Jagannath
+1  A: 

By the way, you can use __is_base_of from "type_traits" introduced in std::tr1 (MSCV 2008 compiler has intrinsics support for that).

Sergius
+2  A: 

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.

jon hanson