Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use typedef/using from templated base class in derived class

In accessing a using from a base class with a templated base class, I've run into an issue with verbosity if nothing else. In the below code, the derived class attempts to use the my_type from its base class.

template <typename T>
class Base {
    public:
        using mytype = T;
};

template <typename T>
class Derived : public Base<T>{
    public:
        // using the base's mytype here
        static typename Base<T>::mytype func() { return 0;}
};

However, in practice I'm finding this to be an awful lot of characters for what seems like it should be simpler. if the Base class was not templated then it wouldn't require the <T> or the typename (obviously).

In my real issue, I have a great deal of classes deriving from the base and I want to simplify this if possible. What I have at the moment is like the next example, where I'm just adding an additional using to get the type from the base class, but it feels like an extra layer that I shouldn't need to have.

template <typename T>
class Derived : public Base<T>{
    public:
        using base_type = typename Base<T>::mytype;
        static base_type func() { return 0;}
};

this may seem like a silly issue, but the number of times the base class's mytype is used in the derived classes makes it pretty hideous in the former case. Is there a right way to get the types out of the templated base that maintains readability?

like image 438
Ryan Haining Avatar asked Nov 06 '13 15:11

Ryan Haining


2 Answers

This is a well known quirk of the language for which there is no real solution. Lookup in templates is done in two separate steps, during the first phase of lookup before instantiation non-dependent names are resolved to their meanings, while in the second phase dependent names are resolved after instantiation.

The division of the two phases was included in the language to provide some sanity for the template developer that does not know where the template will be instantiated. The first phase of lookup is done at the point where the template is defined, and it can be reasoned about by the developer at exactly that point. At one point or another, the template will do operations that depend on the arguments, and those cannot be resolved where the template is defined, since the template arguments are not yet fixed. Those names are considered dependent, and lookup is postponed to the second phase, after substitution of the template arguments, so that ADL can kick in.

How does this relate to your particular problem? When you inherit from a non-template base, the developer has fixed what the base is, and that can be looked up at the point of the template definition as you expect. But when the base depends on a template argument, the definition of the base class is not known at the place of the derived template definition. In particular, without substituting the type in, the compiler cannot possibly know whether there is a specialization for this particular type. This implies that during the first phase the compiler cannot assume anything at all about the base and thus lookup cannot search into it.

The direct way of using a typedef in a base is for the developer of the derived template to explicitly tell the compiler that it wants a type and that the type will be defined in the instantiation of the base template. The key point here is that the compiler knows nothing about the base, but the developer can require that uses of this template comply with a contract in which the instantiation of the base must have that nested type. The developer is free to add constraints on the types of her template, the compiler is not.

The syntax for that is the one in your first block: typename Base<T>::type to refer to the type, which tells the compiler that the contract of use requires that whatever T is used to instantiate Derived, the user is required to ensure that Base<T> will contain a nested member type that is a type (typename). As a short hand, you can opt for the second approach: create a local typedef that will be found inside Derived and resolved to that. In that case, regular lookup during the first phase will find the nested typedef, determine that it refers to a dependent name and postpone the complete lookup for the second phase.

While this is not an answer to the question of whether there is a better way (readability wise), I hope it provides some understanding as of why things are like they are. The decision while designing the language was not arbitrary [it might or not be ideal, some people believe the first phase is unneeded and unwanted, but it was not arbitrary]

like image 55
David Rodríguez - dribeas Avatar answered Sep 28 '22 03:09

David Rodríguez - dribeas


Maybe I'm missing something obvious here, but you can simply use using on the type directly without having to give it a new name:

template <typename T>
class Derived : public Base<T>{
public:
    using typename Base<T>::mytype;
    static mytype func() { return 0;}
};

You can even decide if the using-declaration for mytype should go into the public, protected or private section.

like image 23
Daniel Frey Avatar answered Sep 28 '22 02:09

Daniel Frey