16

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;
} 
Georg Fritzsche
  • 97,545
  • 26
  • 194
  • 236
dicroce
  • 45,396
  • 28
  • 101
  • 140
  • That's pretty damn cool voodoo. – zneak Apr 13 '10 at 23:06
  • 7
    If you are interested in those things, get a copy of Alexandrescus *"Modern C++ Design"*. – Georg Fritzsche Apr 13 '10 at 23:12
  • 3
    I think this is all over the place, e.g http://stackoverflow.com/questions/2631585/c-how-to-require-that-one-template-type-is-derived-from-the-other/2631677#2631677. Note that the test itself happens at compile-time, but you obtain the result at runtime (returning from a function) where you can hardly use it for anything useful. – UncleBens Apr 13 '10 at 23:16
  • It's a neat trick, but only for educational purposes. I'd never use anything like that in code I use because generally in good design you don't explicitly compare types. – wilhelmtell Apr 14 '10 at 01:34
  • ^^ I probably should never say "never". But yeah. – wilhelmtell Apr 14 '10 at 01:44
  • `boost::is_base_of`, `boost::is_base_and_derived`. It's fun, but fairly well known by now. Note that the boost examples allow you to get access to the information at compile time which is much more interesting :) – Matthieu M. Apr 14 '10 at 11:27
  • This is now a part of C++11's type traits http://en.cppreference.com/w/cpp/types/is_base_of – Trevor Hickey Dec 18 '13 at 07:09

6 Answers6

11

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
  • 868,454
  • 176
  • 1,908
  • 1,964
  • `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 Apr 13 '10 at 23:37
  • @gf: I meant as opposed to declaring a pointer to a `Dervied`. – SLaks Apr 13 '10 at 23:40
6

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.

EMP
  • 59,148
  • 53
  • 164
  • 220
5

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
  • 277,958
  • 43
  • 419
  • 720
5

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
  • 8,722
  • 2
  • 37
  • 61
2

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

Sergey Podobry
  • 7,101
  • 1
  • 41
  • 51
1

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) );
}
Peixu Zhu
  • 2,111
  • 1
  • 15
  • 13