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.

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • Probably oversight. Hard to foresee all the possible "oops"'s you can make with a new language feature. – GManNickG Feb 02 '13 at 17:39
  • @GManNickG that's what I was assuming, but I didn't find a library issue [on the issues list](http://cplusplus.github.com/LWG/lwg-active.html). – Johannes Schaub - litb Feb 02 '13 at 17:52
  • 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? – Nawaz Feb 02 '13 at 18:43
  • @Nawaz "that" is the rejection of the argument by the function. – Johannes Schaub - litb Feb 02 '13 at 19:18
  • @JohannesSchaub-litb: In case you had misplaced it: http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#submit_issue – Howard Hinnant Feb 02 '13 at 23:43
  • @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. – Johannes Schaub - litb Feb 03 '13 at 00:16

3 Answers3

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.

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • 1
    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). – Johannes Schaub - litb Feb 02 '13 at 18:17
  • @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"` strings – Andy Prowl Feb 02 '13 at 18:26
  • I am following the rationale given in http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2640.pdf , "A programmer's view of initialization kinds" and "Core Idea". – Johannes Schaub - litb Feb 02 '13 at 18:33
  • @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? – Andy Prowl Feb 02 '13 at 18:45
  • 1
    @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. – Andy Prowl Feb 02 '13 at 18:49
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.

Leo Chapiro
  • 13,678
  • 8
  • 61
  • 92
  • 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{10, "foo"}`. But I didn't write that. I expect that the argument I passed is accepted by `void f(pair);`, rather. – Johannes Schaub - litb Feb 02 '13 at 17:54
  • The expression you used didn't compile by me: f({10, "foo"}); That is why I assumed you meant std::vector f (10, "foo"); – Leo Chapiro Feb 02 '13 at 17:59
  • @duDE: you are likely to be using a compiler that doesn't support uniform initialization. What compiler are you using? – Andy Prowl Feb 02 '13 at 18:00
  • Visual Studio 2012 Update 1 (Version 11.0.51106.01 to be precise) – Leo Chapiro Feb 02 '13 at 18:02
  • Do you have the November 2102 CTP installed? – Andy Prowl Feb 02 '13 at 18:03
  • I would assume I did, the Update one is from 28.11.2012 – Leo Chapiro Feb 02 '13 at 18:06
  • 1
    The CTP is just a technology preview, so it's probably something different from an Update. Anyway, you can verify [here](http://rise4fun.com/Vcpp/wg) that the program compiles with the CTP. – Andy Prowl Feb 02 '13 at 18:13
  • @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!) – GManNickG Feb 02 '13 at 19:05
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.
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • 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? – Johannes Schaub - litb Feb 02 '13 at 20:58
  • (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 v = { ... }` or `std::vector f() { return { ... }; }`, on which I would agree that the current rules seem suboptimal. – Johannes Schaub - litb Feb 02 '13 at 21:03
  • @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... – Nicol Bolas Feb 02 '13 at 21:12
  • 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? – Johannes Schaub - litb Feb 02 '13 at 21:20
  • @JohannesSchaub-litb: The general argument against it is [laid out in this paper.](http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2575.pdf) That's what convinced the committee to introduce "copy-list-initialization". I don't entirely agree with it, but that's the argument. – Nicol Bolas Feb 02 '13 at 21:45
  • 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). – Johannes Schaub - litb Feb 02 '13 at 21:53
  • 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'. – Johannes Schaub - litb Feb 02 '13 at 21:56
  • @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. – Nicol Bolas Feb 02 '13 at 21:59
  • @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. – Andy Prowl Feb 02 '13 at 22:29