2

I'm trying to make a generic code that will cause a compile error if B is not an ancestor of D. What I came up with:

template<typename B, typename D>
struct assert_base_of {
    enum {value = sizeof(B::D)};
}

It doesn't work. When I 'call' it like this:

assert_base_of<A2,A1>::value;

I get the following error (g++ 4.8.2):

main.cpp:16:22: error: ‘D’ is not a member of ‘A2’

It looks like the template parameter D doesn't get substituted for A1. Can anyone explain and suggest a solution please?

binduck
  • 71
  • 7
  • What's wrong with `static_assert (std::is_base_of::value, "B must be a base of D")` ? – Alexandre C. Jun 20 '15 at 13:19
  • I'm not allowed to use std::is_base_of and even if I could, I still want to know why D doesn't get substituted :) – binduck Jun 20 '15 at 13:30
  • "Allowed" by whom? If someone have placed arbitrary constraints on your possible solution set, you should state them in the question to avoid potentially wasting our time. – Lightness Races in Orbit Jun 20 '15 at 13:31
  • 1
    `B::D` is interpreted as a member (not a type) because `B` is dependent here. Anyway, you can't get a base class like this -- you need to cast a pointer from `D` to `B` to check if `D` inherits `B`. See @Veritas answer on how to implement `is_base_of`. – Alexandre C. Jun 20 '15 at 13:33
  • @AlexandreC. Thanks for the answer, but what does it mean 'dependent'? Is there a way to override this? and also, when doing sizeof(A::B) out of template, my program did compile when B was a base of A and didn't compile when it wasn't the case. What made it work? – binduck Jun 20 '15 at 13:45
  • 1
    `B` dependent means that it depends on a template parameter (here it *is* a template parameter). When you write `B::foo`, `foo` is always interpreted as the name of a member of `B` if `B` is dependent (this is because the compiler looks twice at your template, and it does not know in the first pass what `B` will end up to be). If you want `foo` to name a type, you must use `typename B::foo`. If you want `foo` to name a template, you must use `template B::foo`. – Alexandre C. Jun 20 '15 at 13:51
  • Also, see http://stackoverflow.com/questions/2910979/how-does-is-base-of-work if you are not allowed to use C++11 and you want a C++03 solution. – Alexandre C. Jun 20 '15 at 13:55
  • 1
    Also, what you are observing in your non-template `sizeof(A::B)` case is the injected class name of `B` into `B` (typename`B` gets "injected" into `B`, and `A` inherits it -- `A::A` exists too as a type). This is a dark corner of C++ that you should avoid, since it behaves differently with templates. See http://stackoverflow.com/questions/7025054/ambiguous-injected-class-name-is-not-an-error and related questions. The important thing here is that `A::B` names a type, not a member. – Alexandre C. Jun 20 '15 at 14:04

1 Answers1

3

Inheritance does not enclose the derived class to the base class' scope so it makes no sense to use the scope resolution operator for that. The correct alternative (that also works with multiple inheritance) is to abuse the overload resolution rules:

#include <iostream>
#include <type_traits>

template<typename Base, typename Derived,
       bool = std::is_same<Base, Derived>::value>
struct is_base_of_impl
{

  typedef typename std::remove_cv<Base>::type     no_cv_base;      
  typedef typename std::remove_cv<Derived>::type  no_cv_derived;


  template<typename _Up>
  static std::true_type test(no_cv_derived&, _Up);
  static std::false_type test(no_cv_base&, int);

  //Black Magic
  struct converter
  {
   operator no_cv_derived&();
   operator no_cv_base&() const;
  };

  static const bool value = decltype(test(converter(), 0))::value;
};

template<typename Base, typename Derived>
struct is_base_of_impl<Base, Derived, true>
{ 
    static const bool value = std::is_same<Base, Derived>::value; 
};

template<typename Base, typename Derived>
struct is_base_of
: public std::integral_constant<bool,
               is_base_of_impl<Base, Derived>::value>
{ };


struct A {};
struct B1 : A {};
struct B2 : A {};
struct C : B1, B2 {};

int main()
{
    std::cout << is_base_of<A, B1>::value << "\n";
    std::cout << is_base_of<B1, C>::value << "\n";
    std::cout << is_base_of<A, C>::value << "\n";
    return 0;
}

For more information take a look into these links:

How does `is_base_of` work?

https://groups.google.com/d/msg/comp.lang.c++.moderated/xv4VlXq2omE/--WAroYkW2QJ

Community
  • 1
  • 1
Veritas
  • 2,150
  • 3
  • 16
  • 40