Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Initialization of std::vector<std::string> throws

275 views
Skip to first unread message

Ralf Goertz

unread,
Sep 4, 2019, 8:10:04 AM9/4/19
to
Hi,

why does the declaration

std::vector<std::string> pt({{"One","Two"}});

throw:

terminate called after throwing an instance of 'std::length_error'
what(): basic_string::_M_create

whereas neither

std::vector<std::string> pt({{"One"}});

nor

std::vector<int> pt({{1,2}});

does? Full program:

#include <vector>
#include <string>
#include <iostream>

std::vector<std::string> pt({{"One","Two"}});
int main() {
for (auto p:pt) std::cout<<p<<std::endl;
}

No warning is given during compilation by gcc version 9.2.1 20190820

Sam

unread,
Sep 4, 2019, 8:21:03 AM9/4/19
to
Ralf Goertz writes:

> Hi,
>
> why does the declaration
>
> std::vector<std::string> pt({{"One","Two"}});
>
> throw:
>
> terminate called after throwing an instance of 'std::length_error'
> what(): basic_string::_M_create

Because this is undefined behavior.

> whereas neither
>
> std::vector<std::string> pt({{"One"}});
>
> nor
>
> std::vector<int> pt({{1,2}});

If you actually examine the end result the last declaration, you will
discover, to your surprise, a std::vector<int> with just one value, and that
would hopefully be a big honking clue as to what this is doing.

{ /* something */ } introduces a braced initialization list.

{ { /* something */ } } introduces a braced initialization list with one
value.


{ { "One", "Two" } } attempts to construct a single std::string with two
values. The only overloaded std::string constructor this matches is the one
that takes a beginning iterator and an ending iterator of a sequence that
forms the contents of the constructed std::string.

Since "One" and "Two" decay to two character pointers which do not comprise
a single sequence, you just nuked yourself from high orbit, hence the crash.

> No warning is given during compilation by gcc version 9.2.1 20190820

A compiler has no obligation to warn you when you're about to shoot yourself
in a foot. It would be nice if it did, and in many cases it does; but you
can't rely on your compiler to keep you from shooting yourself in a foot.

Ralf Goertz

unread,
Sep 4, 2019, 8:33:47 AM9/4/19
to
Am Wed, 04 Sep 2019 08:20:51 -0400
schrieb Sam <s...@email-scan.com>:

> Ralf Goertz writes:
>
> > Hi,
> >
> > why does the declaration
> >
> > std::vector<std::string> pt({{"One","Two"}});
> >
> > throw:
> >
> > terminate called after throwing an instance of 'std::length_error'
> > what(): basic_string::_M_create
>
> Because this is undefined behavior.
>
> > whereas neither
> >
> > std::vector<std::string> pt({{"One"}});
> >
> > nor
> >
> > std::vector<int> pt({{1,2}});
>
> If you actually examine the end result the last declaration, you will
> discover, to your surprise, a std::vector<int> with just one value,
> and that would hopefully be a big honking clue as to what this is
> doing.

Hm, I did that and it failed to surprise me.


#include <vector>
#include <string>
#include <iostream>

std::vector<int> pt({{1,2}});
int main() {
for (auto p:pt) std::cout<<p<<std::endl;
}

gives:
1
2

here. Just as I expected.
> { /* something */ } introduces a braced initialization list.
>
> { { /* something */ } } introduces a braced initialization list with
> one value.

But didn't we need to use two braces for container intialization?

Bonita Montero

unread,
Sep 4, 2019, 8:43:24 AM9/4/19
to
Am 04.09.2019 um 14:09 schrieb Ralf Goertz:
> Hi,
> why does the declaration
> std::vector<std::string> pt({{"One","Two"}});

To less curly brackets! Thre must be at least 42!

Or this:
vector<string> thatsright { "One", "Two" };

Pavel

unread,
Sep 5, 2019, 11:26:50 PM9/5/19
to
my guess is the above pt declarator is roughly equivalent to
pt(vector<int>(initializer_list<int>(initializer_list<int>(1,2)))) --
which just happens to give you an expected result. I think it is
undefined or implementation-specific what default copy constructor does
on initializer_list because the data members of it are not defined by
the standard.
>
> here. Just as I expected.
>> { /* something */ } introduces a braced initialization list.
>>
>> { { /* something */ } } introduces a braced initialization list with
>> one value.
>
> But didn't we need to use two braces for container intialization?
>
for vector<int>, because there is constructor with count and value, you
need something like = { 1, 2}; (note =) for {1,2} to be interpreted as
initializer_list; for vector<string> you should be able to initialize
with {"One", "Two"} directly.

-Pavel

Tim Rentsch

unread,
Sep 18, 2019, 11:37:41 AM9/18/19
to
This seems wrong to me. Please note I am not saying it IS wrong,
only that it seems to me to be wrong. More specifically I don't
think undefined behavior plays a role. I tried reading the
Standard to verify that but found it impenetrable in this area.
Can someone help find the relevant passages (and an accompanying
explanation would also be appreciated)?

>> here. Just as I expected.
>>
>>> { /* something */ } introduces a braced initialization list.
>>>
>>> { { /* something */ } } introduces a braced initialization list with
>>> one value.
>>
>> But didn't we need to use two braces for container intialization?
>
> for vector<int>, because there is constructor with count and value, you
> need something like = { 1, 2}; (note =) for {1,2} to be interpreted as
> initializer_list; for vector<string> you should be able to initialize
> with {"One", "Two"} directly.

I'm pretty sure what you say about vector<int> isn't right. A
declaration like

vector<int> foo { 10, 1 };

gives a vector of length 2, not a vector of length 10. If the
braces were parentheses

vector<int> bas ( 10, 1 );

we would get a vector of length 10, using the ( count, value )
constructor.

Disclaimer: the comments above are based solely on experimental
results, not on understanding what the Standard says about it.

Ralf Goertz

unread,
Nov 22, 2019, 6:12:54 AM11/22/19
to
Am Wed, 4 Sep 2019 14:33:39 +0200
schrieb Ralf Goertz <m...@myprovider.invalid>:
Since nobody has explained that satisfactorily yet (also see Tim's
answer to Pavel) and I just stumbled over it again, I'd like to follow
up. In the following situation double braces are necessary. But why?
This kind of problem led to my original belief that for containers they
were always needed.

#include <vector>
#include <iostream>

struct Foo {
std::vector<int> v;
Foo(std::vector<int> v_=std::vector<int>()) : v(v_){}
};

int main() {
Foo f{{4,5}}; //double braces needed
for (auto r:f.v)
std::cout<<r<<std::endl;
return 0;
}


With single braces I get:
problem.cc: In function ‘int main()’:
problem.cc:10:14: error: no matching function for call to ‘Foo::Foo(<brace-enclosed initializer list>)’
10 | Foo f{4,5};
| ^
problem.cc:6:14: note: candidate: ‘Foo::Foo(std::vector<int>)’
6 | Foo(std::vector<int> v_=std::vector<int>()) : v(v_){}
| ^~~
problem.cc:6:14: note: candidate expects 1 argument, 2 provided
problem.cc:4:8: note: candidate: ‘Foo::Foo(const Foo&)’
4 | struct Foo {
| ^~~
problem.cc:4:8: note: candidate expects 1 argument, 2 provided
problem.cc:4:8: note: candidate: ‘Foo::Foo(Foo&&)’
problem.cc:4:8: note: candidate expects 1 argument, 2 provided



Chris Vine

unread,
Nov 22, 2019, 8:44:13 AM11/22/19
to
I do not have all the previous exchanges available so I may be
repeating what others have said, but the reason why single braces fail
is because you are passing two arguments to a conversion constructor of
Foo taking one (or no) argument. As to why double braces work, my guess
is that (ignoring elision) the inner braces perform an implicit
conversion to std::vector<int> via std::vector's constructor taking an
initializer_list, constructing a temporary vector with two elements in
the vector, and that this then initializes Foo, using std::vector's move
constructor.

But as I say, that is a guess.

Ralf Goertz

unread,
Nov 22, 2019, 10:03:26 AM11/22/19
to
Am Fri, 22 Nov 2019 13:44:09 +0000
schrieb Chris Vine <chris@cvine--nospam--.freeserve.co.uk>:
Okay I guessed that much, but why can't the compiler do the latter when
there is only one pair of braces? The error message clearly indicates
that an initializer list is considered.

By the way my original question was why a double brace initializer list
doesn't work for

std::vector<std::string> pt({{"1","2"}});

(it compiles but throws a std::length_error because the arguments are
taken as pointers) but works perfectly for

std::vector<int> pt({{1,2}});

On the other hand

std::vector<std::string> pt({{"1","2","3"}});

again works as expected (with or without the parenthesis) which I just
figured out. So the two string element case fails only because the
compiler picks the wrong constructor. I find that odd because the
initializer list seems to be the more obvious choice…

Bo Persson

unread,
Nov 22, 2019, 10:22:44 AM11/22/19
to
It is not *that* obvious to the compiler, as it tries to match an
initializer_list<char> and you supply two char*. Unfortunately that is a
better match for std::string(iterator, iterator), but then of course
fails at runtime as it is not a valid iterator pair.

For *any* number of strings other than 2, the std::string(iterator,
iterator) will immediately be ruled out, so works much better.


You would have a better chance to match the vector's initializer_list
constructor if you actually provided som std::string literals:

std::vector<std::string> pt({{"1"s,"2"s}});



Bo Persson






Alf P. Steinbach

unread,
Nov 22, 2019, 10:30:37 AM11/22/19
to
On 04.09.2019 14:20, Sam wrote:
> Ralf Goertz writes:
>
>> Hi,
>>
>> why does the declaration
>>
>> std::vector<std::string> pt({{"One","Two"}});
>>
>> throw:
>>
>> terminate called after throwing an instance of 'std::length_error'
>>   what():  basic_string::_M_create
>
> Because this is undefined behavior.

That's the short explanation, but it would be even more informative to
mention why.

Like,

#include <iostream>
#include <string>
#include <vector>
using namespace std;

auto main()
-> int
{
const auto& data = "Hello";
std::vector<std::string> v( {{&data[0], &data[4]}} );
std::cout << v.front() << "!" << endl;
}

Result:

Hell!

I.e. it uses the `string` constructor that takes two iterators.


>> whereas neither
>>
>> std::vector<std::string> pt({{"One"}});

This looks like it would use the `string` constructor that takes a
pointer to C string.


>> nor
>>
>> std::vector<int>  pt({{1,2}});
>
> If you actually examine the end result the last declaration, you will
> discover, to your surprise, a std::vector<int> with just one value, and
> that would hopefully be a big honking clue as to what this is doing.

No, this one is more tricky.

Apparently it uses the move constructor of `vector` to take a temporary
`vector` constructed from the inner braces as an `initializer_list`.

Disclaimer: the results are consistent with this explanation, and there
doesn't seem to be any other explanation, but I haven't debugged.


> { /* something */ } introduces a braced initialization list.
>
> { { /* something */ } } introduces a braced initialization list with one
> value.
>
>
> { { "One", "Two" } } attempts to construct a single std::string with two
> values. The only overloaded std::string constructor this matches is the
> one that takes a beginning iterator and an ending iterator of a sequence
> that forms the contents of the constructed std::string.
>
> Since "One" and "Two" decay to two character pointers which do not
> comprise a single sequence, you just nuked yourself from high orbit,
> hence the crash.
>
>> No warning is given during compilation by gcc version 9.2.1 20190820
>
> A compiler has no obligation to warn you when you're about to shoot
> yourself in a foot. It would be nice if it did, and in many cases it
> does; but you can't rely on your compiler to keep you from shooting
> yourself in a foot.

The problem is not the compiler or programmer. The problem IMO is some
political shenanigans in the C++ standardization committee, where they
chose to sabotage Bjarne's vision of uniform initialization by making
initializer lists take precedence in overload resolution. I prefer this
explanation of academics' childish evilness, to sheer incompetence at
that level, because the explanation of incompetence is too frightening.

The political view has so far, to my knowledge, yielded accurate
predictions and postdictions.

For example, the political view of childish sabotage indicates that the
filesystem::path UTF-8 functionality /will/ be sabotaged for Windows in
C++20, along with use of `u` prefix string literals. The `u` literals
don't make much sense in pure *nix-specific code, because literals are
UTF-8 encoded there anyway, and so the change in type doesn't matter
much there. But in Windows, those unfortunate souls who have tried to
embrace the UTF-8 world via `u` literals and filesystem::path, will have
some work to do, including implementing their own UTF-16 to UTF-8
conversion for getting an UTF-8 representation of a path.

- Alf

Chris Vine

unread,
Nov 22, 2019, 11:13:52 AM11/22/19
to
On Fri, 22 Nov 2019 16:03:12 +0100
That's because in the absence of an initializer-list constructor for
Foo, the compiler is obliged to treat 'Foo f{4,5}' as a call to a
constructor taking two arguments, and you haven't provided an initializer
list constructor for Foo. The only constructors available are the
user-provided constructor (which is a combined default constructor and
conversion constructor), and the compiler provided copy constructor and
move constructor. You will see that the compiler did consider these as
candidates, and then (obviously) found no match.

Leaving that aside, I think the main aim is to write intelligible code,
not code which scratches at the margins of C++'s implicit conversions.
So instead of writing 'Foo f{{4,5}}', write 'Foo f{std::vector<int>{4,5}}',
and likewise with your vector of string. Or provide an initializer-list
constructor. You will then be able to understand your intentions when
you come back to it 6 months later.

> By the way my original question was why a double brace initializer list
> doesn't work for
>
> std::vector<std::string> pt({{"1","2"}});
>
> (it compiles but throws a std::length_error because the arguments are
> taken as pointers) but works perfectly for
>
> std::vector<int> pt({{1,2}});
>
> On the other hand
>
> std::vector<std::string> pt({{"1","2","3"}});
>
> again works as expected (with or without the parenthesis) which I just
> figured out. So the two string element case fails only because the
> compiler picks the wrong constructor. I find that odd because the
> initializer list seems to be the more obvious choice…

You have received a separate answer to that.

Tim Rentsch

unread,
Nov 28, 2019, 4:44:49 PM11/28/19
to
Ralf Goertz <m...@myprovider.invalid> writes:

> Am Fri, 22 Nov 2019 13:44:09 +0000
> schrieb Chris Vine <chris@cvine--nospam--.freeserve.co.uk>:
>
>> On Fri, 22 Nov 2019 12:12:43 +0100
>> Ralf Goertz <m...@myprovider.invalid> wrote:
>>
>>> Since nobody has explained that satisfactorily yet (also see Tim's
>>> answer to Pavel) and I just stumbled over it again, I'd like to
>>> follow up. In the following situation double braces are necessary.
>>> But why? This kind of problem led to my original belief that for
>>> containers they were always needed.
>>>
>>> #include <vector>
>>> #include <iostream>
>>>
>>> struct Foo {
>>> std::vector<int> v;
>>> Foo(std::vector<int> v_=std::vector<int>()) : v(v_){}
>>> };
>>>
>>> int main() {
>>> Foo f{{4,5}}; //double braces needed
>>> for (auto r:f.v)
>>> std::cout<<r<<std::endl;
>>> return 0;
>>> }
>>>
>>>
>>> With single braces I get:
>>> problem.cc: In function ?int main()?:
>>> problem.cc:10:14: error: no matching function for call to
>>> ?Foo::Foo(<brace-enclosed initializer list>)? 10 | Foo f{4,5};
>>> | ^
>>> problem.cc:6:14: note: candidate: ?Foo::Foo(std::vector<int>)?
>>> 6 | Foo(std::vector<int> v_=std::vector<int>()) : v(v_){}
>>> | ^~~
>>> problem.cc:6:14: note: candidate expects 1 argument, 2 provided
>>> problem.cc:4:8: note: candidate: ?Foo::Foo(const Foo&)?
>>> 4 | struct Foo {
>>> | ^~~
>>> problem.cc:4:8: note: candidate expects 1 argument, 2 provided
>>> problem.cc:4:8: note: candidate: ?Foo::Foo(Foo&&)?
>>> problem.cc:4:8: note: candidate expects 1 argument, 2 provided
>>
>> I do not have all the previous exchanges available so I may be
>> repeating what others have said, but the reason why single braces fail
>> is because you are passing two arguments to a conversion constructor
>> of Foo taking one (or no) argument. As to why double braces work, my
>> guess is that (ignoring elision) the inner braces perform an implicit
>> conversion to std::vector<int> via std::vector's constructor taking an
>> initializer_list, constructing a temporary vector with two elements in
>> the vector, and that this then initializes Foo, using std::vector's
>> move constructor.
>
> Okay I guessed that much, but why can't the compiler do the latter when
> there is only one pair of braces? The error message clearly indicates
> that an initializer list is considered.

I don't have anything to add about why this example behaves as it
does, but I have two related comments. One, it doesn't surprise
me at all that two pairs are braces are needed. Two, I'm not
sure having it work with only one pair of braces is a good idea;
if anything I suspect it would be more confusing than helpful.
My analogy is initializing structs (for C, I don't know if C++
might have changed here): I know there are rules for omitting
braces in some cases when initializing structs with nested arrays
or nested structs, but it seems insane to take advantage of those
rules. Presumably they were put in for compatibility with early
C code, which makes sense, but not continuing to use them going
forward. Returning to the C++ example, having two pairs of
braces matches my understanding of what's going on; allowing
only one pair of braces to do the same thing would mean I have
to create a more elaborate and more complicated mental model to
understand what the program is doing and how the language works.
I would much rather not to have to create these more elaborate
models. Do you see what I mean?
0 new messages