9

The issue is easily resolved by not being an implicit conversion fiend, but it seems like a strange inconsistency to me. Here's an example:

#include <unordered_set>

void test1(int a, int b); // Overloaded function with two different
void test1(std::unordered_set<char> set); // possible argument types

struct Test2 {
  Test2(int a, int b); // Overloaded constructor with two different
  Test2(std::unordered_set<char> set); // possible argument types
};

int main() {

  test1(123,456); // Testing out the overloaded functions
  test1({'a','b'}); // This works correctly with no ambiguity complaint
  // Neither GCC nor CLang mistake the initializer list of
  // two chars as possibly being two ints instead.

  Test2(123,456); // Testing out the overloaded constructors
  Test2({'a','b'}); // Error: more than one instance of constructor 
                  // "Test::Test" matches the argument list:C/C++(309)
}

// GCC & Clang complain about ambiguity for the implicit conversion in the constructor overload
// GCC gives error: call of overloaded ‘Test2(<brace-enclosed initializer list>)’ is ambiguous
// CLang gives error: call to constructor of 'Test2' is ambiguous

I know that using explicit Test2(int a, int b); resolves the issue in CLang, whereas GCC still gives the same complaint.

Why would two compilers that can deduce the correct types of arguments for overloaded functions refuse to do the same thing for overloaded constructors?

John Schock
  • 126
  • 4
  • 5
    Interesting. Apparently it looks like it could be an intended invocation of a copy or move constructor because `{'a','b'}` can be used to construct a `Test2` with `Test2(int a, int b)`. – user4581301 Jun 09 '22 at 21:07
  • It's just something that made me shrug ‍♂️. I don't expect that there's a better solution than to simply name the type. – John Schock Jun 09 '22 at 21:24
  • 2
    @user4581301 I don't really have the why down, but `Test2{123,456}` calls the `(int,int)` overload; `Test2{{'a', 'b'}}` calls the `(unordered_set)` overload. That may be a solution that doesn't require you to spell out: `Test2(std::unordered_set{'a', 'b'})`. – paolo Jun 09 '22 at 21:26
  • @paolo Using Test2{{'a', 'b'}} does indeed keep the compiler from complaining Hadn't thought about trying that notation at all – John Schock Jun 09 '22 at 21:33
  • 1
    @user4581301 I think you must be right about the copy/move constructor. It must be ambiguous because {'a','b'} by itself could mean make a new Test2 object using (int,int) OR an std::unordered_set. That definitely would explain why there's a complaint in the class case but not in the function case – John Schock Jun 09 '22 at 21:40
  • 1
    The best proof is you won't have this problem with `Test2({'a'});` or `Test2({'a','b','c'});` because they can't match the `int`,`int` constructor. – user4581301 Jun 09 '22 at 21:50
  • 1
    Trying to find the right Standard quote to explain why changing out the brackets for braces solves the problem. I've noticed it in the past, but never looked into it. – user4581301 Jun 09 '22 at 21:52
  • 1
    I think (maybe, possibly) the terms for the `()` versus `{}` constructor calls are *direct initialization* and *list initialization*. Looking through the cppreference pages, but can't find any quotable explanation for what actually differentiates the calls in this case. Unless it is, as @JohnSchock said, that the latter doesn't consider copy constructors. Or as user4581301 said, actually. – Adrian Mole Jun 09 '22 at 22:04
  • 1
    Paolo pointed it out first. Can't find where this is covered in the Standard. It's covered in [**[dcl.init\]**](http://eel.is/c++draft/dcl.init), but I just can't find it. Going to have to give up soon or risk my sanity. There are things mankind was not meant to know. – user4581301 Jun 09 '22 at 22:12
  • 1
    However, deleting the copy and move constructors in the `Test2` class (and the two assignment operators) doesn't clear up the ambiguous call error. :( – Adrian Mole Jun 09 '22 at 22:13
  • 1
    Yeah. Deleted functions still take part in look-up. They are there, but you can't use them. – user4581301 Jun 09 '22 at 22:15

1 Answers1

6

This is a fun one. {'a', 'b'} could be a request to use Test2(int a, int b) to construct a temporary Test2 that can then be used to copy or move construct another Test2. Eg:

Test2 b = {'a', 'b'};

This won't happen with

Test2({'a'});

or

Test2({'a','b','c'});

because they don't match another constructor.

The Quick Solution is to use braces instead of brackets

Test2{{'a','b'}};

but I have yet to find a definitive Standard quote on why this is.

user4581301
  • 33,082
  • 7
  • 33
  • 54
  • 1
    Well, clang-cl is certainly not the Standard but, for the OP's code, as well as the error about an ambiguous call, I get three helpful messages: (1) *message : candidate constructor (the implicit copy constructor)* (2) *message : candidate constructor (the implicit move constructor)* (3) *message : candidate constructor*, indicating the one with the set parameter. – Adrian Mole Jun 09 '22 at 22:21
  • 1
    @AdrianMole I just noticed that G++ actually did state this but my mind blocked it out. For some (bad) reason I totally ignored it – John Schock Jun 09 '22 at 22:47