14

During resolution of an overload of a templated member function of a base class, I observed a different behaviour between g++ (5.2.1-23) and clang (3.8.0), with -std=c++14.

#include <iostream>
#include <type_traits>

struct Base
{
  template <typename T>
  auto a(T t) -> void {
    std::cout<< "False\n";
  }
};

template <bool Bool>
struct Derived : public Base
{

  using Base::a;
  template <typename T, bool B = Bool>
  auto a(T t) -> std::enable_if_t<B, void>
  {
    std::cout<< "True\n";
  }
};

int main()
{
  Derived<true> d;
  d.a(1); // fails with g++, prints "true" with clang
  Derived<false> d2;
  d2.a(1); // fails with clang++, prints "false" with g++
}

The call to Derived<true>::a fails with g++ with the following message:

test.cc: In function ‘int main()’:
test.cc:28:8: error: call of overloaded ‘a(int)’ is ambiguous
   d.a(1);
        ^
test.cc:18:8: note: candidate: std::enable_if_t<B, void> Derived<Bool>::a(T) [with T = int; bool B = true; bool Bool = true; std::enable_if_t<B, void> = void]
   auto a(T t) -> std::enable_if_t<B, void>
        ^
test.cc:7:8: note: candidate: void Base::a(T) [with T = int]
   auto a(T t) -> void {
        ^

and the call to Derived<false>::a fails with clang++ with the following message:

test.cc:32:6: error: no matching member function for call to 'a'
  d2.a(1);
  ~~~^
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.2.1/../../../../include/c++/5.2.1/type_traits:2388:44: note: candidate template ignored: disabled by 'enable_if' [with T = int, B = false]
    using enable_if_t = typename enable_if<_Cond, _Tp>::type;
                                           ^

My guess is that they interpret differently the using Base::a;, and that it isn't considered in clang, whereas it's (maybe too much) considered in g++. What I thought would happen is that if Derived has true as parameter, then the call of a() is dispatched to Derived's implementation, whereas if the parameter is false, the call is dispatched to Base::a.

Are they both wrong? Who is right? Who should I submit a bug report to? Can somebody explain what is going on?

Thanks

V Tolmer
  • 160
  • 9

1 Answers1

3

From 3.3.10/p3 Name hiding [basic.scope.hiding]:

In a member function definition, the declaration of a name at block scope hides the declaration of a member of the class with the same name; see 3.3.7. The declaration of a member in a derived class (Clause 10) hides the declaration of a member of a base class of the same name; see 10.2

Also 7.3.3/p15 The using declaration [namespace.udecl]:

When a using-declaration brings names from a base class into a derived class scope, 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 (8.3.5), cv-qualification, and ref-qualifier (if any) in a base class (rather than conflicting). [ Note: For using-declarations that name a constructor, see 12.9. — end note ] [Example:

struct B {
  virtual void f(int);
  virtual void f(char);
  void g(int);
  void h(int);
};
struct D : B {
  using B::f;
  void f(int); // OK: D::f(int) overrides B::f(int);
  using B::g;
  void g(char); // OK
  using B::h;
  void h(int); // OK: D::h(int) hides B::h(int)
};
void k(D* p)
{
  p->f(1); // calls D::f(int)
  p->f(’a’); // calls B::f(char)
  p->g(1); // calls B::g(int)
  p->g(’a’); // calls D::g(char)
}

— end example ]

This is resolved during member name look-up. Thus, it's before template argument deduction. Consequently, as correctly TC mentioned in the comments Base template function is hidden no matter of SFINAE verdict.

Therefore CLANG is correct and GCC is wrong.

akim
  • 8,255
  • 3
  • 44
  • 60
101010
  • 41,839
  • 11
  • 94
  • 168
  • 2
    It's either hidden and not part of the overload set, or it's visible and so part of the overload set. "Sometimes hidden" makes no sense. – T.C. Dec 08 '15 at 15:42
  • 1
    So, if I understand correctly, g++ is wrong because a conflict with a `using` shouldn't be considered, and clang is wrong because if nothing is found, it should have a look at the `using`, right? So I should submit a bug report on both compilers. – V Tolmer Dec 08 '15 at 15:43
  • @VTolmer Based on the standard wording I believe this is the case. – 101010 Dec 08 '15 at 16:11
  • @T.C. exactly, can't be ad hoc hidden. When true the Derived template function hides the Base template function. When substitution failure occurs Base template function comes forward since the Derived doesn't participate in the overload set. – 101010 Dec 08 '15 at 16:40
  • 3
    No, if it's hidden, it's not in the overload set, period. Name lookup finds a set of candidates, you do template argument deduction and overload resolution on it. You can't say "oh, deduction failed, so let's go add more members to the overload set". – T.C. Dec 08 '15 at 16:45
  • 1
    Indeed. Which means that clang is right. If `Bool` is `true`, then the call is unambiguous (because the template from the base class is hidden) and invokes the template from the derived class, as expected. If `Bool` is `false`, then the candidate set still contains the template from the derived class *only*, but this is eliminated during substitution, resulting in an empty candidate set. Which in turn makes the program ill-formed as per §13.3/3 (N3690). – Arne Vogel Dec 09 '15 at 19:11