Search code examples
c++templateslanguage-design

Why can I access the base class aliased types from the derived class?


In my development journey, today I found something that I do not understand why happens.

This should represent a generic a base unit in the SI:

template<Ratio r, Symbol s>
struct base_unit {
    using ratio = r;
    using symbol = s;
};

So, in my public interface, I am able to define a base dimension like this:

template <typename Dimension>
struct base_dimension {
    using dimension = Dimension;
};

And a concrete base unit like this:

struct mass : public base_dimension<mass> {};

So, I wanted to design a concept that checks for the presence of those aliases inside a unit, like for example, Kilogram:

struct Kilogram: public mass, public base_unit<Kilo, kg> {};

So I went ahead, and I wrote this concept, to constrain future implementors:

template <typename T>
concept Unit = requires {  
    typename T::base_dimension::dimension;
    typename T::base_unit::ratio;
    typename T::base_unit::symbol;
};

But, for my surprise, I am able to write this code:

template <typename T>
concept OtherUnitConcept = requires {  
    typename T::dimension;
    typename T::ratio;
    typename T::symbol;
};
static_assert(Unit<Kilogram>);
static_assert(OtherUnitConcept<Kilogram>);

Why I am able to directly request for those alias to T? The idea is that I am using those inheritance constructs as tag dispatching, and I supposed that I would always should take the full route, T::base_dimension::dimension , since T doesn't have in it's body definition a typename dimension, but base_dimension has one, and I looked surprised to be able to directly be able to found dimension in the construct T::dimension.

Here's a minimal working example


Solution

  • TBH, your question should be Why can I access the base class aliased types from the derived class?

    It's things about qualified name lookup. According to cppref,

    If the lookup of the left hand side name comes up with a class/struct or union name, the name on the right hand side of :: is looked up in the scope of that class (and so may find a declaration of a member of that class or of its base), with the following exceptions

    So, T::dimension can look up T::base_dimension::dimension if T::dimension is not found.