How does the following code work?
typedef char (&yes)[1];
typedef char (&no)[2];
template <typename B, typename D>
struct Host
{
operator B*() const;
operator D*();
};
template <typename B, typename D>
struct is_base_of
{
template <typename T>
static yes check(D*, T);
static no check(B*, int);
static const bool value = sizeof(check(Host<B,D>(), int())) == sizeof(yes);
};
//Test sample
class Base {};
class Derived : private Base {};
//Expression is true.
int test[is_base_of<Base,Derived>::value && !is_base_of<Derived,Base>::value];
-
Note that
Bis private base. How does this work? -
Note that
operator B*()is const. Why is it important? -
Why is
template<typename T> static yes check(D*, T);better thanstatic yes check(B*, int);?
Note: It is reduced version (macros are removed) of boost::is_base_of. And this works on wide range of compilers.
If they are related
Let’s for a moment assume that
Bis actually a base ofD. Then for the call tocheck, both versions are viable becauseHostcan be converted toD*andB*. It’s a user defined conversion sequence as described by13.3.3.1.2fromHost<B, D>toD*andB*respectively. For finding conversion functions that can convert the class, the following candidate functions are synthesized for the firstcheckfunction according to13.3.1.5/1The first conversion function isn’t a candidate, because
B*can’t be converted toD*.For the second function, the following candidates exist:
Those are the two conversion function candidates that take the host object. The first takes it by const reference, and the second doesn’t. Thus the second is a better match for the non-const
*thisobject (the implied object argument) by13.3.3.2/3b1sb4and is used to convert toB*for the secondcheckfunction.If you would remove the const, we would have the following candidates
This would mean that we can’t select by constness anymore. In an ordinary overload resolution scenario, the call would now be ambiguous because normally the return type won’t participate in overload resolution. For conversion functions, however, there is a backdoor. If two conversion functions are equally good, then the return type of them decides who is best according to
13.3.3/1. Thus, if you would remove the const, then the first would be taken, becauseB*converts better toB*thanD*toB*.Now what user defined conversion sequence is better? The one for the second or the first check function? The rule is that user defined conversion sequences can only be compared if they use the same conversion function or constructor according to
13.3.3.2/3b2. This is exactly the case here: Both use the second conversion function. Notice that thus the const is important because it forces the compiler to take the second conversion function.Since we can compare them – which one is better? The rule is that the better conversion from the return type of the conversion function to the destination type wins (again by
13.3.3.2/3b2). In this case,D*converts better toD*than toB*. Thus the first function is selected and we recognize the inheritance!Notice that since we never needed to actually convert to a base class, we can thereby recognize private inheritance because whether we can convert from a
D*to aB*isn’t dependent on the form of inheritance according to4.10/3If they are not related
Now let’s assume they are not related by inheritance. Thus for the first function we have the following candidates
And for the second we now have another set
Since we cannot convert
D*toB*if we haven’t got a inheritance relationship, we now have no common conversion function among the two user defined conversion sequences! Thus, we would be ambiguous if not for the fact that the first function is a template. Templates are second choice when there is a non-template function that is equally good according to13.3.3/1. Thus, we select the non-template function (second one) and we recognize that there is no inheritance betweenBandD!