Calling an explicit constructor with a braced-init list: ambiguous or not?

As far as I can tell, this is a clang bug.

Copy-list-initialization has a rather unintuitive behaviour: It considers explicit constructors as viable until overload resolution is completely finished, but can then reject the overload result if an explicit constructor is chosen. The wording in a post-N4567 draft, [over.match.list]p1

In copy-list-initialization, if an explicit constructor is chosen, the
initialization is ill-formed. [ Note: This differs from other
situations (13.3.1.3, 13.3.1.4), where only converting constructors
are considered for copy-initialization. This restriction only applies
if this initialization is part of the final result of overload
resolution. — end note ]


clang HEAD accepts the following program:

#include <iostream>
using namespace std;

struct String1 {
    explicit String1(const char*) { cout << "String1\n"; }
};
struct String2 {
    String2(const char*) { cout << "String2\n"; }
};

void f1(String1) { cout << "f1(String1)\n"; }
void f2(String2) { cout << "f2(String2)\n"; }
void f(String1) { cout << "f(String1)\n"; }
void f(String2) { cout << "f(String2)\n"; }

int main()
{
    //f1( {"asdf"} );
    f2( {"asdf"} );
    f( {"asdf"} );
}

Which is, except for commenting out the call to f1, straight from Bjarne Stroustrup’s N2532 – Uniform initialization, Chapter 4. Thanks to Johannes Schaub for showing me this paper on std-discussion.

The same chapter contains the following explanation:

The real advantage of explicit is that it renders f1("asdf") an
error. A problem is that overload resolution “prefers” non-explicit
constructors, so that f("asdf") calls f(String2). I consider the
resolution of f("asdf") less than ideal because the writer of
String2 probably didn’t mean to resolve ambiguities in favor of
String2 (at least not in every case where explicit and non-explicit
constructors occur like this) and the writer of String1 certainly
didn’t. The rule favors “sloppy programmers” who don’t use explicit.


For all I know, N2640 – Initializer Lists — Alternative Mechanism and Rationale is the last paper that includes rationale for this kind of overload resolution; it successor N2672 was voted into the C++11 draft.

From its chapter “The Meaning Of Explicit”:

A first approach to make the example ill-formed is to require that all
constructors (explicit and non-explicit) are considered for implicit
conversions, but if an explicit constructor ends up being selected,
that program is ill-formed. This rule may introduce its own surprises;
for example:

struct Matrix {
    explicit Matrix(int n, int n);
};
Matrix transpose(Matrix);

struct Pixel {
    Pixel(int row, int col);
};
Pixel transpose(Pixel);

Pixel p = transpose({x, y}); // Error.

A second approach is to ignore the explicit constructors when looking
for the viability of an implicit conversion, but to include them when
actually selecting the converting constructor: If an explicit
constructor ends up being selected, the program is ill-formed. This
alternative approach allows the last (Pixel-vs-Matrix) example to work
as expected (transpose(Pixel) is selected), while making the
original example (“X x4 = { 10 };“) ill-formed.

While this paper proposes to use the second approach, its wording seems to be flawed – in my interpretation of the wording, it doesn’t produce the behaviour outlined in the rationale part of the paper. The wording is revised in N2672 to use the first approach, but I couldn’t find any discussion about why this was changed.


There is of course slightly more wording involved in initializing a variable as in the OP, but considering the difference in behaviour between clang and gcc is the same for the first sample program in my answer, I think this covers the main points.

Leave a Comment