Why are vector's multi-argument constructors taking construction parameters not marked "explicit"?
Asked Answered
B

3

10

I observed the following vector constructors in the Standard C++ library

explicit vector(size_type n);
vector(size_type n, const T& value, const Allocator& = Allocator());

Is there a reason why the second constructor is not marked explicit? This compiles, and makes me feel bad

void f(vector<string>);

int main() {
  f({10, "foo"});
}

While if I omit the "foo", it doesn't compile and that is what I expect when I pass a pair (compound) value of an int and a string to a function that wants a vector of strings.

Bollworm answered 2/2, 2013 at 17:29 Comment(6)
Probably oversight. Hard to foresee all the possible "oops"'s you can make with a new language feature.Habana
@Habana that's what I was assuming, but I didn't find a library issue on the issues list.Bollworm
This is confusing to me : "While if I omit the "foo", it doesn't compile and that is what I expect when I pass a pair (compound) value of an int and a string to a function that wants a vector of strings." .... What that (in the bold text) is referring to?Macadamia
@Nawaz "that" is the rejection of the argument by the function.Bollworm
@JohannesSchaub-litb: In case you had misplaced it: open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#submit_issueNestornestorian
@HowardHinnant i don't know whether this is an issue at all, because I am way too unfamiliar with the design of the Standards library. I am glad if you could give advices as to what the design principle was on these constructors.Bollworm
G
3

I'm wondering whether it is legitimate in the first place to expect that { ... } always represents a list of container elements when creating a temporary. This seems to be your assumption. IMO the one-argument constructor needs to be declared as explicit to avoid undesidered conversion sequences or meaningless assignments such as:

vector<int> x = 3;

On the other hand, for the two-argument version, the only way this constructor can be called when a temporary is created is with the use of curly braces, and the programmer is well aware of what he's putting in there. For instance, it is quite clear to me that 10 and "hello" are not meant to represent a list of container elements, because 10 is not a string.

If I really wanted to pass in a vector of 10 elements initialized to "hello", I would be bothered by having to write f(vector(10, "hello")) instead of just doing f({10, "hello"}).

So to sum it up: while the one-argument constructor needs to be declared as explicit, I believe this is not mandatory for the two-argument value, because not everything which is inside a pair of curly braces should be interpreted as a list of container elements.

Guardianship answered 2/2, 2013 at 18:6 Comment(5)
I could intend to pass { 10, "foo" } to a pair of int and string or to an aggregate like struct ArticleEntry { int count; string name; }; (to indicate that there are 10 foos available). These are not containers, but I do not see yet why that matters. It seems intuitive to me that the argument conveys a value of both of these examples, while it seems also intuitive to me that the argument does not convey the value of a vector of string (on the latter of which you do agree with me).Bollworm
@JohannesSchaub-litb: I think I covered this in the last paragraph of my answer: whether or not {10, "hello") conveys the value of a vector of strings depends on how used one is to think that a vector of strings can be constructed that way. IMO it is not a necessity to declare that constructor as explicit, but rather a matter of taste. Personally, I am not against seeing {10, "hello"} as conveying the value of a vector of 10 "hello" stringsGuardianship
I am following the rationale given in open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2640.pdf , "A programmer's view of initialization kinds" and "Core Idea".Bollworm
@JohannesSchaub-litb: From the viewpoint of that paper then yes, it should be declared as as explicit because it is case (e) of "Core Idea". Has that proposal been adopted?Guardianship
@JohannesSchaub-litb: Oh, I see it has been adopted, because the suggested wordings are in the current version of the Standard. Well, then I guess it is an oversight.Guardianship
P
1

While if I omit the "foo", it doesn't compile and that is what I expect when I pass a pair (compound) value of an int and a string to a function that wants a vector of strings.

No, you don't pass a pair of int and a string but you create a vector of size 10 with content of strings like "foo". There is nothing wrong on it! I can figure some situation where it may be usefull to create a vector contains the equal strings from the beginning.

Profound answered 2/2, 2013 at 17:43 Comment(8)
I only see one int and one string in the argument {10, "foo"}, instead of 10 strings. What you are stating applies to a function argument of the form vector<string>{10, "foo"}. But I didn't write that. I expect that the argument I passed is accepted by void f(pair<int, string>);, rather.Bollworm
The expression you used didn't compile by me: f({10, "foo"}); That is why I assumed you meant std::vector<std::string> f (10, "foo");Profound
@duDE: you are likely to be using a compiler that doesn't support uniform initialization. What compiler are you using?Guardianship
Visual Studio 2012 Update 1 (Version 11.0.51106.01 to be precise)Profound
Do you have the November 2102 CTP installed?Guardianship
I would assume I did, the Update one is from 28.11.2012Profound
The CTP is just a technology preview, so it's probably something different from an Update. Anyway, you can verify here that the program compiles with the CTP.Guardianship
@dude: Update 1 will not contain the NovCTP. The NovCTP is essentially a beta release that is known to be relatively untested/has bugs, and it's release is to gain feedback and find more bugs. (Therefore they won't include this in an official release, that would introduce bugs on purpose!)Habana
S
0

that is what I expect when I pass a pair (compound) value of an int and a string to a function that wants a vector of strings.

Well, there's your problem.

{...} is not a "compound value". It is not a list. It says, "initialize an object using these values". If the object in question is an aggregate, it will use aggregate initialization. If the object in question is a non-aggregate type, it will pick a constructor to call, based on the matching constructors of the type and the various rules for braced-init-lists in C++11.

You shouldn't think of {10, "foo"} as a list of two values. It is an initializer that contains two values. It could be used with a std::pair<int, const char *>, and so forth.

The reason why std::vector's constructor isn't explicit is precisely to allow this construct. The single-argument constructor is explicit because otherwise, implicit conversion rules would allow this:

std::vector<T> v = 5; //???

Or, more importantly:

void Foo(const std::vector<T> &v);

Foo(5); //???

We don't want integers to be implicitly convertible to std::vectors. However, when you're using an initializer, it's more reasonable to allow a wider range of "implicit" conversions, because you can see the {} syntax there.

With the single-argument case, it isn't clear what the user means. With the {} syntax, it is clear what the user means: to initialize the object.

Foo({10, "foo"}); //Initializes the first argument given the values.
Solenoid answered 2/2, 2013 at 20:47 Comment(9)
Your answer appears to be contradictional to the rationale of the current C++ rules. It also appears to do away with explicit completely. Why would it make sense for list initialization to error out when it picks an explicit constructor at all, then?Bollworm
(I know that discussion is discouraged on SO, so let's keep it short). Independent of the current rules, let me say that I am not convinced about "With the {} syntax, it is clear what the user means: to initialize the object.". Because there is no "the object". There is "a parameter of a function 'Foo'". What type that parameter has is unknown to the programmer. Overload resolution will have to discover that first. There is (IMHO) a conceptual difference to std::vector<T> v = { ... } or std::vector<T> f() { return { ... }; }, on which I would agree that the current rules seem suboptimal.Bollworm
@JohannesSchaub-litb: "Why would it make sense for list initialization to error out when it picks an explicit constructor at all, then?" If you look back at uniform initialization's standardization process, some people didn't want it to error out on explicit constructors ever. Others did, and they defined those circumstances based on copy-initialization. Though I do agree that the T v = {} should be able to use explicit constructors, since the typename is right there...Solenoid
from your last sentence in the previous comment I inferr that you disagree that {...}-argument passing can use explicit constructors in a function call. Why do you do so, what is the ratioale?Bollworm
@JohannesSchaub-litb: The general argument against it is laid out in this paper. That's what convinced the committee to introduce "copy-list-initialization". I don't entirely agree with it, but that's the argument.Solenoid
that appears to be a pre-version of the paper I linked above in a comment to the answer of @Andy. explicit is used when the parameters are merely construction parameters rather than being values that are copied or converted to the class (hence "conversion" constructor). Hence if you say that the (int size, T defValue) constructor should not be explicit you seem to be saying that a size and a default value for the elemets together form the value of a vector. Why doesn't only the size without the default value do that? (it is explicit).Bollworm
Your argument for why it is explicit was "because that allows conversion from 'e'". But that IMO is not a valid answer to the question, unless you agree that there is a broken work-around in effect in that it breaks coversion from '{e}' in addition to that from 'e'.Bollworm
@JohannesSchaub-litb: Well that's a point of view. explicit historically was used to keep implicit conversions from happening for unrelated types. An integer is not a vector, so implicitly turning it into one is almost certainly the wrong thing. It's there to keep unpleasant implicit conversions from happening, while still allowing explicit ones. Personally, I generally agree with Stroustrup that {} syntax should be able to call explicit constructors in most if not all circumstances. But that's the way the spec is defined.Solenoid
@JohannesSchaub-litb: IMHO the answer to "Why doesn't only the size without the default value do that?" is that it does indeed describe a value, but since the constructor has only one argument, it must be declared as explicit to avoid undesired implicit conversion sequences. Of course, if we embrace your viewpoint that 1) these are "parameters that drive the construction of a value" rather than "a direct representation of a value themselves", and 2) only in the second case the implicit construction should be allowed, then yes, what we're looking at is an oversight.Guardianship

© 2022 - 2024 — McMap. All rights reserved.