问题
initializer list constructors in C++ often cause trouble; for example
using std::vector;
using std::string;
vector<string> v{3}; // vector of three empty strings
vector<int> u{3}; // vector of one element with value 3
(Just to clarify, I mean <int>
constructor is an initializer list constructor, while the <string>
one is not.)
The int
case matches the initializer list constructor, while the string
case doesn't. This is somewhat ugly, and often causes trouble. It was also noted in an early chapter (item 7) in Scott Meyers' Effective Modern C++, and he describes it as a somewhat unpleasant part of the standard, that whenever an initializer list constructor is available, compilers will jump through hoops to try to match it, giving it priority over every single other constructor.
Of course, the int case can be easily fixed by changing u{3}
to u(3)
, but that's not the point.
Is this desirable behavior? Has there been any discussion or plans by the C++ standard committee to fix this type of ambiguity / unpleasantness? One example would be requiring initializer list constructors to be called like this: vector<int> u({3})
, which is already currently legal.
回答1:
Has there been any discussion or plans by the C++ standard committee to fix this type of ambiguity / unpleasantness?
There have been many fixes to initialization since C++11. For instance, you initially couldn't copy construct aggregates using list-initialization (CWG 1467). This really minor fix broke some code in an undesirable way that lead to a new issue to refine that previous fix to undo it if there's an initializer_list
constructor (CWG 2137). It's hard to touch anything in these clauses without lots of unexpected consequences and breaking code, even in small cases. I doubt there will be a push to make any kind of large change to initialization in the future. At this point, the amount of code breakage would be tremendous.
The best solution is just to be aware about the pitfalls of initialization and be careful about what you're doing. My rule of thumb is to only use {}
s when I deliberately need the behavior that {}
provides, and ()
otherwise.
Note that this isn't really any different from the more well-known pitfall of:
vector<int> a{10}; // vector of 1 element
vector<int> b(10); // vector of 10 elements
One example would be requiring initializer list constructors to be called like this:
vector<int> u({3})
, which is already currently legal.
You have the same problem you had before, for the same reasons:
vector<int> u({3}); // vector of one element: 3
vector<string> v({3}); // vector of three elements: "", "", and ""
Even if you were to require the former (which would be impossible), you couldn't make the latter ill-formed.
回答2:
First is an uniform initializer, and it was introduced to solve ambiguity of language. The problem was called Most Vexing Parse in relation to declaring variables, intialized by "round" () parenthesis. MVP is a kind of ambiguity resolution in code similar to following:
class SomeInitClass;
void bleh()
{
int foo(SomeInitClass());
}
foo
here actually is a prototype of a function that takes as its parameter a function that returns a Bar, and the foo
function's return value is an int
. Essentially if something looks like prototype, C++ treats it as such.
int foo{SomeInitClass{}};
SomeInitClass{}
would always creates a temporary. int foo{...}
always would create a variable.
While those two lines work differently:
vector<string> v{3}; // vector of three empty strings
vector<int> u{3}; // vector of one element with value 3
they do bear same semantics, they are declaration of variables. That way of how it works (and fact that you can declare constructor that takes initializer list as parameter) does bear much significance on C++ language, which adopts the concept of concealing true value and amount of actions behind its syntax.
It's not inconsistency, not a major one at least. vector<string>
and vector<int>
are NOT same class and do NOT have same constructors, because they are not identical instantiations of template std::vector. To avoid confusionone may use aliases and use slightly different syntax.
StringCollecton v{3}; //three strings;
IntCollection u = {3}; // or {{3}}
Of course, StringCollecton test = {3};
will not work in this case, because 3
is not literal that can be casted into proper stored type.
Because you can have only one initializer in declaration, setting values of created string container would look so:
std::vector<std::string> test{3, {"string"}}; // all values initialized by "string"
std::vector<std::string> test{{"string1","",""}}; // constructor introduced in C++11
While I can be lazy and left out innermost braces, the are the syntax sugar allows me show that it IS the initializer_list in there.
来源:https://stackoverflow.com/questions/48635853/are-there-any-plans-in-c-standard-to-address-inconsistency-of-initializer-list