15

I have encountered a very strange overload failure. I am able to isolate the problem, but I cannot for the life of it figure out what goes wrong.

The code is the following

#include <vector>
#include <iostream>

template<class X>
class Foo
{
public:
  Foo(const std::initializer_list<X> &A){}
  Foo(size_t n){}
};

class Bar
{
public:
  Bar() = default;
  Bar(const Foo<size_t> &A, bool a=true, bool b=true){};
};

int main()
{
  Bar A({1,2});
}

Compiling results in

$ clang++ -std=c++14 so.cpp

so.cpp:21:11: error: call to constructor of 'Bar' is ambiguous
      Bar A({1,2});
          ^ ~~~~~
so.cpp:12:11: note: candidate is the implicit move constructor
    class Bar
          ^
so.cpp:12:11: note: candidate is the implicit copy constructor
so.cpp:16:7: note: candidate constructor
      Bar(const Foo<size_t> &A, bool a=true, bool b=true){};
      ^
1 error generated.

The two things that get rid of the problem are:

  • Removing Foo(size_t n).
  • Changing the constructor to Bar(const Foo<size_t> &A).

Obviously I want to keep all features. So: What goes wrong? How can I solve it?

Tom de Geus
  • 5,625
  • 2
  • 33
  • 77

1 Answers1

15

What goes wrong?

Bar A({1,2});

Can be interpreted as:

Bar A(Bar{Foo<std::size_t>(1), (bool)2 /*, true*/ });

or

Bar A(Foo<std::size_t>{1,2} /*, true, true*/);

so ambiguous call.

How can I solve it?

It depends of which result you expect, adding explicit might help for example.

Making explicit Foo(size_t n) would allow only:

Bar A(B{Foo<std::size_t>(1), (bool)2 /*, true*/ });
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Why does the compiler give the implicit move ctor of `Bar` as a candidate then? –  Jun 03 '18 at 16:55
  • syntax `{..}` allows to call non explicit constructor. – Jarod42 Jun 03 '18 at 16:57
  • @Jarod42 I mean, the candidates you wrote in your answer are different from the ones the compiler gave. –  Jun 03 '18 at 17:00
  • 1
    Simply adding `explicit` doesn't compile in GCC https://ideone.com/zR1dTZ – Killzone Kid Jun 03 '18 at 17:04
  • 1
    There are 2 ways to force `initializer_list` constructor, either explicitly `Bar A(std::initializer_list{1,2})`or by using curly brackets `Bar A{{1,2}}` – Killzone Kid Jun 03 '18 at 17:08
  • Thanks a lot, that makes it clearer. I realize that 'best' solution depends on my needs, which is this case making the `Bar` constructor `explicit`, as proposed by @KillzoneKid. In any case, your answer might be more complete if that option is included? – Tom de Geus Jun 03 '18 at 18:12
  • 1
    @TomdeGeus Brace initializer {...} is what you need to call all and any of your constructors and it should also save you some headache in the future https://ideone.com/0DQF5X – Killzone Kid Jun 03 '18 at 19:47
  • @KillzoneKid Thanks! That is very instructive. – Tom de Geus Jun 04 '18 at 06:52