This question already has answers here:
Overload resolution: assignment of empty braces
(2 answers)
Closed 5 years ago.
I ran into a real-life WTF moment when I discovered that the code below outputs "pointer".
#include <iostream>
#include <utility>
template<typename T>
struct bla
{
static void f(const T*) { std::cout << "pointer\n"; }
static void f(const T&) { std::cout << "reference\n"; }
};
int main()
{
bla<std::pair<int,int>>::f({});
}
Changing the std::pair<int,int> template argument to an int or any other primitive type, gives the (for me at least) expected "ambiguous overload" error. It seems that builtin types are special here, because any user-defined type (aggregate, non-trivial, with defaulted constructor, etc...) all lead to the pointer overload being called. I believe the template is not necessary to reproduce it, it just makes it simple to try out different types.
Personally, I don't think that is logical and I would expect the ambiguous overload error in all cases, regardless of the template argument. GCC and Clang (and I believe MSVC) all disagree with me, across C++11/14/1z. Note I am fully aware of the bad API these two overloads present, and I would never write something like this, I promise.
So the question becomes: what is going on?
Oh, this is nasty.
Per [over.ics.list]p4 and p7:
4 Otherwise, if the parameter is a non-aggregate class X and overload resolution per 13.3.1.7 chooses a single best constructor of X to perform the initialization of an object of type X from the argument initializer list, the implicit conversion sequence is a user-defined conversion sequence with the second standard conversion sequence an identity conversion. [...]
[...]
6 Otherwise, if the parameter is a reference, see 13.3.3.1.4. [Note: The rules in this section will apply for initializing the underlying temporary for the reference. -- end note] [...]
[...]
7 Otherwise, if the parameter type is not a class:
[...]
(7.2) -- if the initializer list has no elements, the implicit conversion sequence is the identity conversion. [...]
The construction of a const std::pair<int,int> temporary from {} is considered a user-defined conversion. The construction of a const std::pair<int,int> * prvalue, or a const int * prvalue, or a const int temporary object are all considered standard conversions.
Standard conversions are preferred over user-defined conversions.
Your own find of CWG issue 1536 is relevant, but mostly for language lawyers. It's a gap in the wording, where the standard doesn't really say what happens for initialisation of a reference parameter from {}, since {} is not an expression. It's not what makes the one call ambiguous and the other not though, and implementations are managing to apply common sense here.
Related
In the code below, with both versions of the print method present the first call resolves to the one with initializer_list. If I comment out the definition with initializer_list, the program seamlessly uses the vector version. In the first case I was expecting the compiler to complain!
#include <iostream>
#include <vector>
using namespace std;
void print(const vector<int>& v1){
cout << "vector \n";
}
void print(const initializer_list<int>& il) {
cout << "init list \n";
}
int main() {
print({1,2,3,4,5});
return 0;
}
This is how overload resolution works in C++. Both versions of print are viable for overload resolution.
print(const vector<int>& v1) is a viable function for overload resolution because input initializer list {1,2,3,4,5} in the caller is implicitly convertible to a std::vector<int>.
print(const initializer_list<int>& il) is a viable function for overload resolution cause the input type in the caller perfectly matches.
When both overloads are into play print(const initializer_list<int>& il) is chosen as the best viable function cause it's a perfect match and a perfect match has a higher priority in overload resolution than an implicit conversion.
Overload resolution works by expressing the operations required to convert the argument to the parameter type as a conversion sequence, and then ranking the conversion sequences according to some rules.
Since we are initializing references, [over.ics.ref]/2 applies:
When a parameter of reference type is not bound directly to an argument expression, the conversion sequence is the one required to convert the argument expression to the underlying type of the reference according to 13.3.3.1. Conceptually, this conversion sequence corresponds to copy-initializing a temporary of the underlying type with the argument expression. Any difference in top-level cv-qualification is subsumed by the initialization itself and does not constitute a conversion.
So this allows us to use the same conversion sequence rules as if the code were:
void o_print(initializer_list<int> ol);
void o_print(vector<int> o1);
i.e. with non-const objects as parameters instead of const references.
Now:
According to [over.ics.list]/2, conversion of a braced list of int to std::initializer_list<int> is an identity conversion. (It wouldn't be if the list members needed promotion or conversion to int).
According to [over.ics.list]/4, conversion of a braced list to non-aggregate class (std::vector<int> here) chooses a constructor of std::vector by overload resolution, and this sequence is defined as a user-defined conversion sequence.
Finally we are ready to apply the ranking rules now. An identity conversion is ranked higher than a user-defined conversion sequence, so the initializer_list<int> version wins.
NB. All references are to C++14.
This question is regarding std::initializer_list, and why it is allowed to initialise primitive types. Consider the following two functions:
void foo(std::string arg1, bool arg2 = false);
void foo(std::string arg1, std::deque<std::string> arg2, bool arg3 = false);
Why is it that, when calling foo like this:
foo("some string", { });
The first overload is picked, instead of the second? Well, actually not why it's picked, it's because { } can be used to initialise anything, including primitive types. My question is the reasoning behind this.
std::initializer_list takes { args... }, and as such cannot have indeterminate length at the time of compilation. Attempting to do something like bool b = { true, true } gives error: scalar object 'b' requires one element in initialiser.
While it might have seemed like a good idea to allow uniform initialisation, the fact is that this is confusing and entirely unexpected behaviour. Indeed, how is the compiler able to do this, without some magic in the background doing std::initializer_list things?
Unless { args... } is a C++ lexical construct, in which case my point still stands: why is it allowed to be used in the initialisation of primitive types?
Thanks. I had quite the bug-hunting session here, before realising that the wrong overload was being called. Spent 10 minutes figuring out why.
That {} syntax is a braced-init-list, and since it is used as an argument in a function call, it copy-list-initializes a corresponding parameter.
§ 8.5 [dcl.init]/p17:
(17.1) — If the initializer is a (non-parenthesized) braced-init-list, the object or reference is list-initialized (8.5.4).
§ 8.5.4 [dcl.init.list]/p1:
List-initialization is initialization of an object or reference from a braced-init-list. Such an initializer is
called an initializer list, and the comma-separated initializer-clauses of the list are called the elements of the
initializer list. An initializer list may be empty. List-initialization can occur in direct-initialization or copy-initialization contexts; [...]
For a class-type parameter, with list-initialization, overload resolution looks up for a viable constructor in two phases:
§ 13.3.1.7 [over.match.list]/p1:
When objects of non-aggregate class type T are list-initialized (8.5.4), overload resolution selects the constructor
in two phases:
— Initially, the candidate functions are the initializer-list constructors (8.5.4) of the class T and the argument list consists of the initializer list as a single argument.
— If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list.
but:
If the initializer list has no elements and T has a default constructor, the first phase is omitted.
Since std::deque<T> defines a non-explicit default constructor, one is added to a set of viable functions for overload resolution. Initialization through a constructor is classified as a user-defined conversion (§ 13.3.3.1.5 [over.ics.list]/p4):
Otherwise, if the parameter is a non-aggregate class X and overload resolution per 13.3.1.7 chooses a single
best constructor of X to perform the initialization of an object of type X from the argument initializer list,
the implicit conversion sequence is a user-defined conversion sequence with the second standard conversion
sequence an identity conversion.
Going further, an empty braced-init-list can value-initialize its corresponding parameter (§ 8.5.4 [dcl.init.list]/p3), which for literal types stands for zero-initialization:
(3.7) — Otherwise, if the initializer list has no elements, the object is value-initialized.
This, for literal types like bool, doesn't require any conversion and is classified as a standard conversion (§ 13.3.3.1.5 [over.ics.list]/p7):
Otherwise, if the parameter type is not a class:
(7.2) — if the initializer list has no elements, the implicit conversion sequence is the identity conversion.
[ Example:
void f(int);
f( { } );
// OK: identity conversion
— end example ]
Overload resolution checks in first place if there exists an argument for which a conversion sequence to a corresponding parameter is better than in another overload (§ 13.3.3 [over.match.best]/p1):
[...] Given these definitions, a viable function F1 is defined to be a better function than another viable function
F2 if for all arguments i, ICSi(F1) is not a worse conversion sequence than ICSi(F2), and then:
(1.3) — for some argument j, ICSj(F1) is a better conversion sequence than ICSj(F2), or, if not that, [...]
Conversion sequences are ranked as per § 13.3.3.2 [over.ics.rank]/p2:
When comparing the basic forms of implicit conversion sequences (as defined in 13.3.3.1)
(2.1) — a standard conversion sequence (13.3.3.1.1) is a better conversion sequence than a user-defined conversion sequence or an ellipsis conversion sequence, and [...]
As such, the first overload with bool initialized with {} is considered as a better match.
Unfortunately, {} does not actually indicate an std::initializer_list. It is also used for uniform initialization. Uniform initialization was intended to fix the problems of the piles of different ways C++ objects could be initialized but ended up just making things worse, and the syntactic conflict with std::initializer_list is fairly awful.
Bottom line is that {} to denote an std::initializer_list and {} to denote uniform initialization are two different things, except when they're not.
Indeed, how is the compiler able to do this, without some magic in the
background doing std::initialiser_list things?
The aforementioned magic most assuredly exists. { args... } is simply a lexical construct and the semantic interpretation depends on context- it is certainly not an std::initializer_list, unless the context says it is.
why is it allowed to be used in the initialisation of primitive types?
Because the Standards Committee did not properly consider how broken it was to use the same syntax for both features.
Ultimately, uniform init is broken by design, and should realistically be banned.
My question is the reasoning behind this.
The reasoning behind it is simple (albeit flawed). List-initialization initializes everything.
In particular, {} stands for "default" initializing the object it corresponds to; Whether this means that its initializer_list-constructor is called with an empty list, or that its default constructor is called, or that it is value-initialized, or that all of an aggregates subobjects are initialized with {}, etc. is irrelevant: It is supposed to act as a universal initializer for any object that the above can be applied to.
If you wanted to call the second overload, you'd have to pass e.g. std::deque<std::string>{} (or pass three arguments in the first place). That is the current modus operandi.
While it might have seemed like a good idea to allow uniform
initialisation, the fact is that this is confusing and entirely
unexpected behaviour.
I wouldn't call it "entirely unexpected" by any means. What is confusing about list-initializing primitive types? It is absolutely vital for aggregates - but there's not that big of a step from aggregate types to arithmetic ones, as no initializer_list is involved in both cases. Don't forget that it can e.g. be useful to prevent narrowing as well.
std::initialiser_list takes { args... }, and as such cannot have
indeterminate length at the time of compilation.
Well, technically speaking,
std::initializer_list<int> f(bool b) {
return b? std::initializer_list<int>{} : std::initializer_list<int>{1};
}
When I compile this program with either gcc-4.6.3 or gcc-4.7.2 the compiler gives me an error about the overloaded call being ambiguous:
#include <iostream>
#include <functional>
class Scott
{
public:
void func(const bool b = true)
{
std::cout << "Called func() with a boolean arg" << std::endl;
}
void func(std::function<void(void)> f)
#ifdef WITH_CONST
const
#endif
{
std::cout << "Called func() with a std::function arg" << std::endl;
}
};
int main (int argc, char *argv[])
{
Scott s;
s.func([] (void) { });
}
However, if I make the overloaded function const, it compiles fine & calls the method I did not expect!
devaus120>> g++ -Wall -std=c++11 -DWITH_CONST wtf.cxx
devaus120>> ./a.out
Called func() with a boolean arg
So, I have 2 questions:
Is it a compiler bug that this compiles when the overloaded method is made const?
How can I ensure the correct overloaded function gets invoked? (Need to cast the argument somehow?)
TIA.
Scott. :)
Actually gcc is correct! Because lambda is not a function but a closure object of class type! Really! You can even inherit from it :) ... even multiple times from different lambdas...
So, according 8.5/16:
[...]
— If the destination type is a (possibly cv-qualified) class type:
[...]
— Otherwise, if the source type is a (possibly cv-qualified) class type, conversion functions are considered. The applicable conversion functions are enumerated (13.3.1.5), and the best one is chosen through overload resolution (13.3). The user-defined conversion so selected is called to convert the initializer expression into the object being initialized. If the conversion cannot be done or is ambiguous, the initialization is ill-formed.
and 13.3.1.5:
Under the conditions specified in 8.5, as part of an initialization of an object of nonclass type, a conversion function can be invoked to convert an initializer expression of class type to the type of the object being initialized. Overload resolution is used to select the conversion function to be invoked. Assuming that “cv1 T” is the type of the object being initialized, and “cv S” is the type of the initializer expression, with S a class type, the candidate functions are selected as follows:
-- The conversion functions of S and its base classes are considered. Those non-explicit conversion functions that are not hidden within S and yield type T or a type that can be converted to type T via a standard conversion sequence (13.3.3.1.1) are candidate functions. For direct-initialization, those explicit conversion functions that are not hidden within S and yield type T or a type that can be converted to type T with a qualification conversion (4.4) are also candidate functions. Conversion functions that return a cv-qualified type are considered to yield the cv-unqualified version of that type for this process of selecting candidate functions. Conversion functions that return “reference to cv2 X” return lvalues or xvalues, depending on the type of reference, of type “cv2 X” and are therefore considered to yield X for this process of selecting candidate functions.
so finally, result of conversion function is a function pointer which would implicitly converted to bool...
you may check this series of conversions with the following simple code:
#include <iostream>
#include <iomanip>
int main()
{
std::cout << std::boolalpha << []{ return 0; } << '\n';
}
the output will be true...
Here is few ways to workaround... you definitely need something because both functions are suitable after overload resolution. Btw, adding const, to signature of the second one, just exclude it because you've got a mutable instance of Scott and again you'll get compile error if declare it w/ const modifier.
So, you can do:
explicit cast (as mentioned in comments)... yeah, long to type...
declare the second foo w/ template parameter Func. Depending on what you are going to do then, here is few options: it can be converted to std::function on assign (if you want to store it to some member), or in case of immediate call you'll even get some optimization (by eliminate conversion to std::function)
more complex way is to declare both functions w/ template parameter and use std::enable_if to turn one of them OFF depending on std::is_same<bool, T> for example (or check for callable/function type)
use type dispatching (yeah, again w/ templated functions)
... I guess it's enough :)
Is this program legal?
struct X { X(const X &); };
struct Y { operator X() const; };
int main() {
X{Y{}}; // ?? error
}
After n2672, and as amended by defect 978, 13.3.3.1 [over.best.ics] has:
4 - However, when considering the argument of a constructor or user-defined conversion function that is a candidate [...] by 13.3.1.7 [...] when the initializer list has exactly one element and a conversion to some class X or reference to (possibly cv-qualified) X is considered for the first parameter of a constructor of X [...], only standard conversion sequences and ellipsis conversion sequences are considered.
This seems rather perverse; it has the result that specifying a conversion using a list-initialization cast is illegal:
void f(X);
f(Y{}); // OK
f(X{Y{}}); // ?? error
As I understand n2640, list-initialization is supposed to be able to replace all uses of direct-initialization and copy-initialization, but there seems no way to construct an object of type X from an object of type Y using only list-initialization:
X x1(Y{}); // OK
X x2 = Y{}; // OK
X x3{Y{}}; // ?? error
Is this the actual intent of the standard; if not, how should it read or be read?
The original intent of 13.3.3.1p4 is to describe how to apply the requirement in 12.3p4 that:
4 - At most one user-defined conversion (constructor or conversion function) is implicitly applied to a single value.
Before defect 84, 13.3.3.1p4 was almost purely informative:
4 - In the context of an initialization by user-defined conversion (i.e., when considering the argument of a user-defined conversion function; see 13.3.1.4 [over.match.copy], 13.3.1.5 [over.match.conv]), only standard conversion sequences and ellipsis conversion sequences are allowed.
This is because 13.3.1.4 paragraph 1 bullet 2 and 13.3.1.5p1b1 restrict the candidate functions to those on class S yielding type T, where S is the class type of the initializer expression and T is the type of the object being initialized, so there is no latitude for another user-defined conversion conversion sequence to be inserted. (13.3.1.4p1b1 is another matter; see below).
Defect 84 repaired the auto_ptr loophole (i.e. auto_ptr<Derived> -> auto_ptr<Base> -> auto_ptr_ref<Base> -> auto_ptr<Base>, via two conversion functions and a converting constructor) by restricting the conversion sequences allowable for the single parameter of the constructor in the second step of class copy-initialization (here the constructor of auto_ptr<Base> taking auto_ptr_ref<Base>, disallowing the use of a conversion function to convert its argument from auto_ptr<Base>):
4 - However, when considering the argument of a user-defined conversion function that is a candidate by 13.3.1.3 [over.match.ctor] when invoked for the copying of the temporary in the second step of a class copy-initialization, or by 13.3.1.4 [over.match.copy], 13.3.1.5 [over.match.conv], or 13.3.1.6 [over.match.ref] in all cases, only standard conversion sequences and ellipsis conversion sequences are allowed.
n2672 then adds:
[...] by 13.3.1.7 [over.match.list] when passing the initializer list as a single argument or when the initializer list has exactly one element and a conversion to some class X or reference to (possibly cv-qualified) X is considered for the first parameter of a constructor of X, [...]
This is clearly confused, as the only conversions that are a candidate by 13.3.1.3 and 13.3.1.7 are constructors, not conversion functions. Defect 978 corrects this:
4 - However, when considering the argument of a constructor or user-defined conversion function [...]
This also makes 13.3.1.4p1b1 consistent with 12.3p4, as it otherwise would allow unlimited application of converting constructors in copy-initialization:
struct S { S(int); };
struct T { T(S); };
void f(T);
f(0); // copy-construct T by (convert int to S); error by 12.3p4
The issue is then what the language referring to 13.3.1.7 means. X is being copy or move constructed so the language is excluding applying a user-defined conversion to arrive at its X argument. std::initializer_list has no conversion functions so the language must be intended to apply to something else; if it isn't intended to exclude conversion functions, it must exclude converting constructors:
struct R {};
struct S { S(R); };
struct T { T(const T &); T(S); };
void f(T);
void g(R r) {
f({r});
}
There are two available constructors for the list-initialization; T::T(const T &) and T::T(S). By excluding the copy constructor from consideration (as its argument would need to be converted via a user-defined conversion sequence) we ensure that only the correct T::T(S) constructor is considered. In the absence of this language the list-initialization would be ambiguous. Passing the initializer list as a single argument works similarly:
struct U { U(std::initializer_list<int>); };
struct V { V(const V &); V(U); };
void h(V);
h({{1, 2, 3}});
Edit: and having gone through all that, I've found a discussion by Johannes Schaub that confirms this analysis:
This is intended to factor out the copy constructor for list initialization
because since we are allowed to use nested user defined conversions, we
could always produce an ambiguous second conversion path by first invoking
the copy constructor and then doing the same as we did for the other
conversions.
OK, off to submit a defect report. I'm going to propose splitting up 13.3.3.1p4:
4 - However, when considering the argument of a constructor or user-defined conversion function that is a candidate:
by 13.3.1.3 [over.match.ctor] when invoked for the copying of the temporary in the second step of a class copy-initialization, or
by 13.3.1.4 [over.match.copy], 13.3.1.5 [over.match.conv], or 13.3.1.6 [over.match.ref] in all cases,
only standard conversion sequences and ellipsis conversion sequences are considered; when considering the first argument of a constructor of a class X that is a candidate by 13.3.1.7 [over.match.list] when passing the initializer list as a single argument or when the initializer list has exactly one element, a user-defined conversion to X or reference to (possibly cv-qualified) X is only considered if its user-defined conversion is specified by a conversion function. [Note: because more than one user-defined conversion is allowed in an implicit conversion sequence in the context of list-initialization, this restriction is necessary to ensure that a converting constructor of X, called with a single argument a that is not of type X or a type derived from X, is not ambiguous against a constructor of X called with a temporary X object itself constructed from a. -- end note]
The version of clang 3.1 shipped with XCode 4.4 agrees with your interpretation and rejects X{Y{}};. As do I, after re-reading the relevant parts of the standard a few times, FWIW.
If I modify X's constructor to take two arguments, both of type const X&, clang accepts the statement Y y; X{y,y}. (It crashes if I try X{Y{},Y{}}...). This seems to be consistent with 13.3.3.1p4 which demands user-defined conversions to be skipped only for the single-element case.
It seems that the restriction to standard and ellipsis conversion sequences was added initially only in cases where another user-defined conversion has already taken place. Or at least that is how I read http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#84.
It's interesting how the standard is careful to apply the restriction only to the second step of copy initialization, which copies from a temporary which already has the correct type (and was obtain potentially through a user-defined conversion!). Yet for list-initialization, no similar mechanism seems to exists...
Several comments on a recent answer of mine, What other useful casts can be used in C++, suggest that my understanding of C++ conversions is faulty. Just to clarify the issue, consider the following code:
#include <string>
struct A {
A( const std::string & s ) {}
};
void func( const A & a ) {
}
int main() {
func( "one" ); // error
func( A("two") ); // ok
func( std::string("three") ); // ok
}
My assertion was that the the first function call is an error, becauuse there is no conversion from a const char * to an A. There is a conversion from a string to an A, but using this would involve more than one conversion. My understanding is that this is not allowed, and this seems to be confirmed by g++ 4.4.0 & Comeau compilers. With Comeau, I get the following error:
"ComeauTest.c", line 11: error: no suitable constructor exists
to convert from "const char [4]" to "A"
func( "one" ); // error
If you can point out, where I am wrong, either here or in the original answer, preferably with reference to the C++ Standard, please do so.
And the answer from the C++ standard seems to be:
At most one user-defined conversion
(constructor or conversion function)
is implicitly applied to a single value.
Thanks to Abhay for providing the quote.
I think the answer from sharptooth is precise. The C++ Standard (SC22-N-4411.pdf) section 12.3.4 titled 'Conversions' makes it clear that only one implicit user-defined conversion is allowed.
1 Type conversions of class objects can be specified by
constructors and by conversion
functions. These
conversions are called user-defined conversions and are used
for implicit type conversions (Clause
4), for
initialization (8.5), and for explicit type conversions (5.4,
5.2.9).
2 User-defined conversions are applied only where they are
unambiguous (10.2, 12.3.2).
Conversions obey the
access control rules (Clause 11). Access control is applied after
ambiguity resolution (3.4).
3 [ Note: See 13.3 for a discussion of the use of conversions
in function calls as well as examples
below. —end
note ]
4 At most one user-defined conversion (constructor or conversion
function) is implicitly applied to a
single
value.
That's true, only one implicit conversion is allowed.
Two conversions in a row may be performed with a combination of a conversion operator and a parameterized constructor but this causes a C4927 warning - "illegal conversion; more than one user-defined conversion has been implicitly applied" - in VC++ for a reason.
As the consensus seems to be already: yes you're right.
But as this question / answers will probably become the point of reference for C++ implicit conversions on stackoverflow I'd like to add that for template arguments the rules are different.
No implicit conversions are allowed for arguments that are used for template argument deduction. This might seem pretty obvious but nevertheless can lead to subtle weirdness.
Case in point, std::string addition operators
std::string s;
s += 67; // (1)
s = s + 67; // (2)
(1) compiles and works fine, operator+= is a member function, the template character parameter is already deduced by instantiating std::string for s (to char). So implicit conversions are allowed (int -> char), results in s containing the char equivalent of 67, e.g. in ASCII this would become 'C'
(2) gives a compiler error as operator+ is declared as a free function and here the template character argument is used in deduction.
The C++ Programming Language (4th. ed.) (section 18.4.3) says that
only one level of user-defined
implicit conversion is legal
That "user-defined" part makes it sound like multiple implicit conversions may be allowed if some are between native types.