6

Let's consider the code bellow where base members are declared in the derived class. GCC and Clang do not agree on which member is going to be hidden:

template <class T>
concept C = true;

struct base
    {
    template <class T>
    void foo0 (T&&);

    template <class T>
    void foo1(T&&) requires C<T>;

    template <C T>
    void foo2(T&&);

    void foo3(C auto &&);

    template <C T, class U>
    void foo4(T&&, U&&);

    template <C T, class U>
    void foo5(T&&, U&&);
    };

struct derived
    : base
    {
    using base::foo0;
    using base::foo1;
    using base::foo2;
    using base::foo3;
    using base::foo4;
    using base::foo5;

    template <class T>
    void foo0(T&&);

    template <class T>
    void foo1(T&&);

    template <class T>
    void foo2(T&&);

    void foo3(auto &&);

    template <class T, class U>
    void foo4(T&&, U&&);

    template <class T, class U>  //inversed template parmeter order
    void foo5(U&&, T&&);         
    };

When derived member function are called, GCC and Clang does not always agree on which function should be called: (compiler explorer link)

void g(derived d, int i) //                              Description of the difference
    {//Function called       |  by GCC   |  by Clang   | between base and derived member
                         //  -----------------------------------------              
    d. foo0(i);          //  |  derived  |  derived    | same signature
    d. foo1(i);          //  |  base     |  base       | trailing require clause
    d. foo2(i);          //  |  base     |  derived    | constrained template parameter
    d. foo3(i);          //  |  base     |  derived    | constrained function parameter
    d. foo4(i,i);        //  |  base     |  derived    | constrained template parameter
    d. foo5(i,i);        //  |  base     |  base       | reversed template parameter order
                                                            // with constraint.
    }

Morally, I don't see way foo1 foo2 and foo3 should be different cases.

Which compiler is right?

The paragraph of the standard that specifies whither a base member declaration is hidden or not is [namespace.udecl]/14:

When a using-declarator brings declarations from a base class into a derived class, member functions and member function templates in the derived class override and/or hide member functions and member function templates with the same name, parameter-type-list ([dcl.fct]), trailing requires-clause (if any), cv-qualification, and ref-qualifier (if any), in a base class (rather than conflicting). Such hidden or overridden declarations are excluded from the set of declarations introduced by the using-declarator.

After a first reading, it looks like Clang is applying the standard at the letter. But...

But what means the letter here? What is suppose to mean equivalent parameter-type-list for a template without its template-head? Consider the case of foo5, it is clear Clang considers the template-head, at least the template parameter order.

Oliv
  • 17,610
  • 1
  • 29
  • 72
  • I already considered that part of standard as defect to ignore template parameters and return type. SFINAE (based on template parameter) have similar issue. Return type is more complex with covariance. They add *trailing requires clause* in C++20, but it seems not handle Concept :-/ – Jarod42 May 21 '20 at 11:46
  • @Jarod42 I suppose GCC take care of these cases too as one could expect. [compiler-explorer-link](https://godbolt.org/z/vYmNFH). If this paragraph was entirely removed from the standard, overload resolution will ultimately select the derived member. Actually I wonder what are the cases that this paragraph is taking care of that overload resolution could not take care more appropriately? – Oliv May 21 '20 at 12:26
  • Without that paragraph, overload resolution would lead to ambiguous calls very often :-/ Don't know if wording can be "fixed" for template though because of backward compatibility... – Jarod42 May 21 '20 at 12:42
  • @Jarod42. OK. Consider one erase this name hiding paragraph and that the best viable function selection was tweaked by modifying [\[over.match.best\]](http://eel.is/c++draft/over.match.best#2.7) so that the treatment that is done to inherited constructor is applied to base member functions declared in the derived class. What problem could arise? I do believe that this is actually what GCC is doing. – Oliv May 21 '20 at 13:37
  • Name hiding is a feature: to not change behavior of derived class which doesn't use `using`. And I meant, there would be several way to change behavior for "better", but existing code might rely on existing rules... :/ – Jarod42 May 21 '20 at 13:53

0 Answers0