Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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;
like image 533
Jonas Avatar asked May 10 '17 06:05

Jonas


People also ask

Why does CTAD ignore the constructor taking (typename identity<T>)?

CTAD ignores the constructor taking (typename Identity<T>::type, unsigned long) due to the non-deduced context, so CTAD uses only (T, long) for deduction.

Can constructor arguments be deduced from class template arguments?

However, sometimes constructors themselves are templated, which breaks the connection that CTAD relies on. In those cases, the author of the class template can provide “deduction guides” that tell the compiler how to deduce class template arguments from constructor arguments.

How does CTAD decide which constructor to call?

Once the type to construct has been chosen, overload resolution to determine which constructor to call happens normally. CTAD doesn’t affect how the constructor is called. For MyAdvancedPair (and std::pair), the deduction guide’s signature (taking arguments by value, notionally) affects the type chosen by CTAD.

How do you resolve an object by type in Unity?

Resolving an Object by Type Unity provides a method named Resolve that you can use to resolve an object by type, and optionally by providing a registration name. Registrations that do not specify a name are referred to as default registrations.


1 Answers

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.
};
like image 56
Stephan Lechner Avatar answered Sep 20 '22 11:09

Stephan Lechner