Is there any potential semantic difference when I use trailing comma during uniform initialization?
std::vector< std::size_t > v1{5, }; // allowed syntax
std::vector< std::size_t > v2{10};
Can I use trailing comma to make compiler to select std::vector::vector(std::initializer_list< std::size_t >) constructor instead of std::vector::vector(std::size_t, const std::size_t &) or are there any other tricks with mentioned syntax?
Can I use it to detect is there std::initializer_list-constructor overloading?
Considering the following code, which constructor must be selected?
struct A { A(int) { ; } A(double, int = 3) { ; } };
A a{1};
A b{2, };
This code is accepted by gcc 8 and A(int) is selected in both cases.
First, The C++ grammar rules makes the trailing , optional for braced-init-list. To quote dcl.init/1
A declarator can specify an initial value for the identifier being
declared. The identifier designates a variable being initialized. The
process of initialization described in the remainder of [dcl.init]
applies also to initializations specified by other syntactic contexts,
such as the initialization of function parameters ([expr.call]) or the
initialization of return values ([stmt.return]).
initializer:
brace-or-equal-initializer
( expression-list )
brace-or-equal-initializer:
= initializer-clause
braced-init-list
initializer-clause:
assignment-expression
braced-init-list
braced-init-list:
{ initializer-list ,opt }
{ designated-initializer-list ,opt }
{ }
Secondly, you can't pretty much override the overload resolution system. It will always use the std::initializer_list constructor if you use such syntax and such std::initializer_list constructor is available.
dcl.init.list/2:
A constructor is an initializer-list constructor if its first
parameter is of type std::initializer_list or reference to
possibly cv-qualified std::initializer_list for some type E, and
either there are no other parameters or else all other parameters have
default arguments.
[ Note: Initializer-list constructors are favored over other constructors in list-initialization ([over.match.list])....
The program below prints Using InitList:
#include <iostream>
#include <initializer_list>
struct X{
X(std::initializer_list<double>){ std::cout << "Using InitList\n"; }
X(int){ std::cout << "Using Single Arg ctor\n"; }
};
int main(){
X x{5};
}
Despite the fact that 5 is a literal of type int, it should have made sense to select the single argument constructor since its a perfect match; and the std::initializer_list<double> constructor wants a list of double. However, the rules favour std::initializer_list<double> because its an initializer-list constructor.
As a result, even the program below fails because of narrowing conversion:
#include <iostream>
#include <initializer_list>
struct Y{
Y(std::initializer_list<char>){ std::cout << "Y Using InitList\n"; }
Y(int, int=4){ std::cout << "Y Using Double Arg ctor\n"; }
};
int main(){
Y y1{4777};
Y y2{577,};
Y y3{57,7777};
}
In response to your comment below, "what if there is no overloading with std::initializer_list, or it is not the first constructor's parameter?" - then overload resolution doesn't choose it. Demo:
#include <iostream>
#include <initializer_list>
struct Y{
Y(int, std::initializer_list<double>){ std::cout << "Y Using InitList\n"; }
Y(int, int=4){ std::cout << "Y Using Double Arg ctor\n"; }
};
int main(){
Y y1{4};
Y y2{5,};
Y y3{5,7};
}
Prints:
Y Using Double Arg ctor
Y Using Double Arg ctor
Y Using Double Arg ctor
If there is no initializer-list constructor available, then the {initializer-list...,} initializer pretty much falls back to direct initialization as per dcl.init/16, whose semantics are covered by the proceeding paragraph of dcl.init/16
No. That comma is a concession to make preprocessor macro tricks work without compile errors. It means nothing about your data type or its size.
Related
In code like this:
#include <iostream>
#include <initializer_list>
#include <string>
struct A
{
A() { std::cout << "2" << std::endl; }
A(int a) { std::cout << "0" << std::endl; }
A(std::initializer_list<std::string> s) { std::cout << "3" << std::endl; }
A(std::initializer_list<int> l) { std::cout << "1" << std::endl; }
};
int main()
{
A a1{{}};
}
Why does it call std::initializer_list<int> specification of constructor?
It'll generate ambiguity compilation error if we define, for example, constructor with std::initializer_list<double>. What are the rules of such construction and why is it so specific about std::initializer_list with number as template argument?
If a class has an initializer list constructor, then {whatever goes here} means to pass {whatevergoeshere} as argument to the present constructors (if there are no initializer list constructors, then whatever goes here are passed as arguments).
So let's simplify the setting and ignore the other constructors, because apparently the compilers don't care about them
void f(std::initializer_list<std::string> s);
void f(std::initializer_list<int> l);
For f({{}}) we have this rule
Otherwise, if the parameter type is std::initializer_list and all the elements of the initializer list can be implicitly converted to X, the implicit conversion sequence is the worst conversion necessary to convert an element of the list to X, or if the initializer list has no elements, the identity conversion. This conversion can be a user-defined conversion even in the context of a call to an initializer-list constructor.
Here we have a single element {} and it needs a user defined conversion to initialize std::string and no conversion (identity) for int. Therefore, int is chosen.
For f({{{}}}) the element is {{}}. Can it be converted to int? The rule is
if the initializer list has one element that is not itself an initializer list, the implicit conversion sequence is the one required to convert the element to the parameter type
...
In all cases other than those enumerated above, no conversion is possible.
Can it be converted to std::string? Yes, because it has an initializer list constructor that has a std::initializer_list<char> init parameter. Therefore, std::string is chosen this time.
The difference to A a3({}) is that in such a case, it's not list initialization, but a "normal" initialization with a {} argument (note that one less nesting because of the missing outer braces). Here our two f-functions are called with {}. And since both lists have no elements, for both we have identity conversions and therefore an ambiguity.
The compiler in this case will also consider f(int) and get a tie with the other two functions. But a tie-breaker would apply that declares the int -parameter worse than the initializer_list parameters. So you have a partial order {int} < {initializer_list<string>, initializer_list<int>}, which is the reason for ambiguity, as the best group of conversion sequences does not contain a single candidate, but two.
{} to a scalar type (such as int, double, char*, etc.) is the identity conversion.
{} to a class type other than a specialization of std::initializer_list (e.g., std::string) is a user-defined conversion.
The former beats the latter.
Consider the code below:
#include <iostream>
#include <vector>
void f(std::vector<int> v) {std::cout << __PRETTY_FUNCTION__ << std::endl;}
void f(int n) {std::cout << __PRETTY_FUNCTION__ << std::endl;}
int main()
{
f({42}); // the int overload is being picked up
}
Live on Coliru
I was a bit surprised to realize that in this case the int overload is being picked up, i.e. the output of the program is:
void f(int)
with the warning
warning: braces around scalar initializer [-Wbraced-scalar-init] f({42});
Of course this happens only when I pass a 1-element list as an argument, otherwise the std::vector overload is being picked up.
Why is {42} treated like a scalar and not like a init-list? Is there any way of forcing the compiler to pick the std::vector overload (without explicitly constructing std::vector<int>{42}) even on 1-element lists?
PS: The std::vector has an init-list constructor
vector(std::initializer_list<T> init, const Allocator& alloc = Allocator());
see (7) from cppreference.
Braced initializer has no type, we can't say {42} is an int or std::initializer_list<int>. When it's used as an argument, special rules for overload resolution will be applied for overloaded function call.
(emphasis mine)
Otherwise, if the parameter type is not a class and the initializer list has one element, the implicit conversion sequence is the one required to convert the element to the parameter type
{42} has only one element with type int, then it's exact match for the overload void f(int). While for void f(std::vector<int>) a user-defined conversion is needed. So void f(int) will be picked up here.
Is there any way of forcing the compiler to pick the std::vector overload (without explicitly constructing std::vector<int>{42}) even on 1-element lists?
As a wordaround, you can put additional braces to force the compiler construct a std::initializer_list<int> and then pick up void f(std::vector<int>):
f({{42}});
LIVE
Forcing std::vector overload
int main()
{
f(std::vector<int>{42}); // the vector overload is being picked up now
}
Why isn't the vector(initializer_list) constructor being picked up?
Assume that another header declares a void f(std::set<int> v).
How would you like the compiler to react when faced with f({1}): construct a vector or construct a set?
Consider the code below:
#include <iostream>
#include <vector>
void f(std::vector<int> v) {std::cout << __PRETTY_FUNCTION__ << std::endl;}
void f(int n) {std::cout << __PRETTY_FUNCTION__ << std::endl;}
int main()
{
f({42}); // the int overload is being picked up
}
Live on Coliru
I was a bit surprised to realize that in this case the int overload is being picked up, i.e. the output of the program is:
void f(int)
with the warning
warning: braces around scalar initializer [-Wbraced-scalar-init] f({42});
Of course this happens only when I pass a 1-element list as an argument, otherwise the std::vector overload is being picked up.
Why is {42} treated like a scalar and not like a init-list? Is there any way of forcing the compiler to pick the std::vector overload (without explicitly constructing std::vector<int>{42}) even on 1-element lists?
PS: The std::vector has an init-list constructor
vector(std::initializer_list<T> init, const Allocator& alloc = Allocator());
see (7) from cppreference.
Braced initializer has no type, we can't say {42} is an int or std::initializer_list<int>. When it's used as an argument, special rules for overload resolution will be applied for overloaded function call.
(emphasis mine)
Otherwise, if the parameter type is not a class and the initializer list has one element, the implicit conversion sequence is the one required to convert the element to the parameter type
{42} has only one element with type int, then it's exact match for the overload void f(int). While for void f(std::vector<int>) a user-defined conversion is needed. So void f(int) will be picked up here.
Is there any way of forcing the compiler to pick the std::vector overload (without explicitly constructing std::vector<int>{42}) even on 1-element lists?
As a wordaround, you can put additional braces to force the compiler construct a std::initializer_list<int> and then pick up void f(std::vector<int>):
f({{42}});
LIVE
Forcing std::vector overload
int main()
{
f(std::vector<int>{42}); // the vector overload is being picked up now
}
Why isn't the vector(initializer_list) constructor being picked up?
Assume that another header declares a void f(std::set<int> v).
How would you like the compiler to react when faced with f({1}): construct a vector or construct a set?
I'm new to C++11 and I was wondering how this code works internally:
class MyClass
{
public:
MyClass(int a, double b) {
cout << "ctor()" << endl;
}
};
int main()
{
MyClass i1{4, 7};
return 0;
}
My understanding of the new initializer list is that it is a class std::initializer_list constructed by special syntax { .... } in the code. So how does this class instance created by {4, 7} internally get transformed to a form that fits the constructor to MyClass? Thanks.
I think this is how it happens. Extracted from: Explanation of list initialization at cppreference.com
If the previous stage does not produce a match, all constructors of T participate in overload resolution against the set of arguments that consists of the elements of the braced-init-list, with the restriction that only non-narrowing conversions are allowed. If this stage produces an explicit constructor as the best match for a copy-list-initialization, compilation fails (note, in simple copy-initialization, explicit constructors are not considered at all)
#include <iostream>
struct X {
X(std::initializer_list<int> list) { std::cout << "list" << std::endl; }
X(float f) { std::cout << "float" << std::endl; }
};
int main() {
int x { 1.0f };
X a(1); // float (implicit conversion)
X b{1}; // list
X c(1.0f); // float
X d{1.0f}; // list (narrowing conversion) ARG!!!
// warning: narrowing conversion of '1.0e+0f' from 'float' to 'int'
// inside { } [-Wnarrowing]
}
Is there any other way of removing std::initializer_list from an overload list (i.e., making the non-list ctors more favorable) instead of using the ()-initialization, or at least prohibiting narrowing conversion to happen (apart from turning warning into error)?
I was using http://coliru.stacked-crooked.com/ compiler which uses GCC 4.8.
Actually, a program containing a narrowing conversion in a brace list initializer is ill-formed. I am not sure why the compiler just gives you a warning, but it definitely should issue an error here (FWIW, Clang does that).
Also notice, that this is a narrowing (and therefore illegal) conversion as well:
int x { 1.0f }; // ERROR! Narrowing conversion required
Per paragraph 8.5.4/3 of the C++11 Standard:
List-initialization of an object or reference of type T is defined as follows:
— If T is an aggregate, aggregate initialization is performed (8.5.1). [...]
— Otherwise, if the initializer list has no elements [...]
— Otherwise, if T is a specialization of std::initializer_list<E>, [...]
— Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated
and the best one is chosen through overload resolution (13.3, 13.3.1.7). If a narrowing conversion (see
below) is required to convert any of the arguments, the program is ill-formed. [...]
To be more precise, the Standard only says that a "diagnostic" is required in this case, and a warning is a diagnostic, so the compiler's behavior is conforming - but I believe emitting an error would be a better behavior.
That looks like a compiler error. You should be getting an error instead of a warning. Brace initialization should never implicitly narrow.
From the standard (§ 8.5.4)
struct B {
B(std::initializer_list<int>);
};
B b1 { 1, 2 }; // creates initializer_list<int> and calls constructor
B b2 { 1, 2.0 }; // error: narrowing
You can achieve what you want with std::enable_if.
#include <iostream>
#include <type_traits>
struct X {
template<typename T, typename = typename std::enable_if<std::is_same<T,int>::value>::type>
X(std::initializer_list<T>) { std::cout << "list" << std::endl; }
X(float) { std::cout << "float" << std::endl; }
};
int main() {
X a(1); // float (implicit conversion)
X b{1}; // list
X c(1.0f); // float
X d{1.0f}; // float (yay)
}
Works on both g++4.8 and clang 3.2
You can use -Wno-c++11-narrowing to turn off the errors:
Here's a sample test program:
#include <cstdint>
struct foo {
int32_t a;
};
void foo(int64_t val) {
struct foo A = { val };
}
Compile with clang++-3.8 with just -std=c++11, we get the stated error:
Add -Wno-c++11-narrowing, golden silence :-)
Of course, the narrowing issue might come back to bite you later, but it might occasionally be easier to delay the technical debt pain till later. ymmv :-)