Jonas
Jonas

Reputation: 7017

Using own class name to resolve type in a deduced context

I answered this question using a C++ construction that I'm not familiar with. I want to know if this is legal or allowed by both g++ (6.3.0) and clang++ (3.5.0) by mistake. The example is available online:

#include <iostream>

template <typename T>
struct Base
{
    using Type = int;
};

template <typename T>
struct intermediate : Base<T>
{
    // 'Type' is not defined here, which is fine
};

template <typename T>
struct Derived : intermediate<T>
{
    using Type = typename Derived<T>::Type; // Is this legal?
//    using Type = typename intermediate<T>::Type; // Normal way of doing it
};

int main()
{
    Derived<void>::Type b = 1;
    std::cout << b << std::endl;
}

Update

As mentioned in the comments (by underscore_d) <T> is not required in the Derived class. That is, this is perfectly fine:

using Type = typename Derived::Type;

Upvotes: 13

Views: 513

Answers (1)

Stephan Lechner
Stephan Lechner

Reputation: 35164

It took me a while to understand the (very good) question, so let me first explain how I understoud the point:

At template level, a base class template's scope is not examined if this base class template depends on a template-parameter (cf, for example, this answer). Hence, a type declared by using Type = int in a class template with template parameter like template <typename T> struct Base will not be visible to a class deriving from this base class template like template <typename T> struct intermediate : Base<T>, unless one uses a qualified name, i.e. typename Base<T>::Type).

At class level, in contrast, such a type declaration would be visible even when used without qualified name. So when instantiating above mentioned template class, a type name that was "invisible" at template level will become visible at the level of the insantiated classes.

So if the subclass at template level would define a type with the same (unqualified) name as the base class template, this would not be apparent at template level. But might such a situation lead to a (probably mismatching) redefinition of a type when instantiating the template, since then both type definitions based on unqualified type names might become visible under the same name?

As pointed out by n.m., who provied this reference in his comment, type definition sticks to the view at the point of template definition, even if at instance level a different type would be resolved:

Non-dependent names are looked up and bound at the point of template definition. This binding holds even if at the point of template instantiation there is a better match.

Thus, it is actually legal, as this will not lead to an invalid redefinition of a type.

However, as the subclass' type definition is actually considered as a new type, it may even be defined in a different manner, which is probably not intended:

template <typename T>
struct Base
{
    using Type = int;
};

template <typename T>
struct intermediate : Base<T>
{
    // 'Type' is not defined here, which is fine
};

template <typename T>
struct Derived : intermediate<T>
{
    //using Type = typename Derived<T>::Type; // legal.
    using Type = const char*; // Legal, too!
};

int main()
{
    Derived<void>::Type b = "OK, but is this actually intended?";
    std::cout << b << std::endl;
}

So the subclass does not "inherit" but "cover" the respective type. If one wants to "inherit", one should write:

struct Derived : intermediate<T>
{
    using typename intermediate<T>::Type; // Inherits the type.
};

Upvotes: 7

Related Questions