c++language-lawyer

GCC vs. Clang: Ambiguous inherited constructor?


This is at the core of a Tensorflow issue.

Consider the following code:

class Option;

class Base
{
public:
  explicit Base(Option opt);
};

class Derived : public Base
{
public:
  using Base::Base;
  explicit Derived(const Option &opt);
};

Derived
doit(const Option &opt)
{
  return Derived(opt);
}

When compiled with GCC versions through 14.2, it fails like so:

<source>: In function 'Derived doit(const Option&)':
<source>:19:21: error: call of overloaded 'Derived(const Option&)' is ambiguous
   19 |   return Derived(opt);
      |                     ^
<source>:6:12: note: candidate: 'Base::Base(Option)'
    6 |   explicit Base(Option opt);
      |            ^~~~
<source>:12:15: note:   inherited here
   12 |   using Base::Base;
      |               ^~~~
<source>:13:12: note: candidate: 'Derived::Derived(const Option&)'
   13 |   explicit Derived(const Option &opt);
      |            ^~~~~~~
<source>:9:7: note: candidate: 'constexpr Derived::Derived(const Derived&)'
    9 | class Derived : public Base
      |       ^~~~~~~
<source>:9:7: note: candidate: 'constexpr Derived::Derived(Derived&&)'
Compiler returned: 1

Clang accepts this code without complaint.

Live example on Golbolt

This seems like a bug either in Clang (for allowing an ambiguous call) or in GCC (for not allowing an unambiguous call). But I am not sure which. So, is this call ambiguous, or not?


Solution

  • The initialization is ambiguous, and clang++ is incorrect.

    A rule which almost applies is [over.match.best] p7: In overload resolution, a viable function F1 is better than viable function F2 if

    • F1 is a constructor for a class D, F2 is a constructor for a base class B of D, and for all arguments the corresponding parameters of F1 and F2 have the same type

    But since the parameter types Option and const Option& are different, this rule should be ignored. No other rules in overload conversion distinguish between an inherited constructor and a plain member constructor. The implicit conversion sequences from a const Option lvalue to parameter type Option or to parameter type const Option& are ambiguous, so Derived(opt) is ill-formed.

    This is already filed as an LLVM bug. Apparently a proposed fix is actively moving.