Search code examples
c++language-lawyermultiple-inheritancename-lookup

Lookup finding the same name through different bases in multiple inheritance with identical values


Say I have a class that inherits from two different classes. They both have an alias like using type_alias = int;. Should looking up type_alias in the scope of the class be int or ambiguous? What if both of the different classes inherited that type alias from the same class?

Here are some other examples:

enum enum_type {
    enumerator
};

template<int>
struct base {
    using type_alias = int;
    using enum enum_type;
};

struct derived : base<0>, base<1> {};

derived::type_alias main() {
    return derived::enumerator;
}

derived::base<2> x;  // injected-class-name
template<int>
struct super_base_template {};

struct super_base : super_base_template<0> {
    using type_alias = int;
    enum enum_type {
        enumerator
    };
    static constexpr int static_member = 2;
};


template<int>
struct base : super_base {};

struct derived : base<0>, base<1> {};

derived::type_alias main() {
    return derived::enumerator;
}

derived::super_base_template<derived::static_member> x;

clang seems to be able to compile all of this, whereas gcc only likes it if they are inherited from literally the same declaration, like in the second snippet and the last line of the first snippet.

Which of these are valid lookups?


Solution

  • [class.member.lookup] describes how a search for a name in the scope of a class is performed. The part relevant to this question is p5.2:

    • Otherwise, if the declaration sets of S(N, Bi) and S(N,C) differ, the merge is ambiguous...

    The wording makes no exception for different declarations denoting the same entity; the lookup is ambiguous even if both found declarations are using type_alias = int;. Conversely, if there is only one declaration (originating from the same base class), that declaration is the unambiguous result of the lookup:

    struct A { using X = int; };
    struct B : A { using Y = int; };
    struct C : A { using Y = int; };
    struct D : B, C {};
    D::X x; // OK
    D::Y y; // ambiguous
    

    Regarding the specific examples: in the first snippet,

    • derived::type_alias is ambiguous as described above,
    • derived::enumerator unambiguously refers to the declaration of enumerator on the second line: using enum enum_type; is equivalent to using enum_type::enumerator; ([enum.udecl]/2), and using-declarators are replaced during lookup by the declarations they name ([basic.lookup.general]/3),
    • derived::base unambiguously refers to the class template base per [temp.local]/4.

    There's a single declaration to be found for every lookup in derived:: in the second snippet, so there's no ambiguity there.