8

This question is already asked most likely, but I did not find the answer.

The code below compiles with gcc but crashes at runtime, with std::length_error (live).

void test(const std::string &value) { std::cout << "string overload: " << value << std::endl; }

//void test(const std::vector<std::string> &) { std::cout << "vector overload" << std::endl; }

int main()
{
    test({"one", "two"});
}

The ability to create a string from the initializer list of strings seems controversial and, for example, does not make it possible to create the overload commented out in the code above.

But even if such construction is allowed, why does it lead to a failure?

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
Yuriy
  • 701
  • 4
  • 18
  • `initializer_list` version of `std::string` is applicable only to list of chars, not list of strings. With list of strings you get standard list initialization of object. Commented overload is ok, when not ambiguous. I.e. if list has more than 2 elements. – sklott Aug 30 '19 at 10:48
  • Note (since this isn't the main question): The problem here comes from `"one"` and `"two"` not being `std::string`s. You can do either `test({{"one"}, {"two"}});` or use C++17 string literals `test({"one"s, "two"s});` (with `using namespace std::literals;`). Either one [will work](https://godbolt.org/z/F91SWI). – Max Langhof Aug 30 '19 at 10:53
  • @Max Langhof, thanks! – Yuriy Aug 30 '19 at 11:41

2 Answers2

11

It calls

string(const char* b, const char* e) 

string ctor overload.

It works only if b and e points to the same string literal. Otherwise it is undefined behaviour.

rafix07
  • 20,001
  • 3
  • 20
  • 33
  • Yes, Thanks. Is there any idea how to achieve the choice of overloading with a vector, preferably without changing the syntax of the call? – Yuriy Aug 30 '19 at 10:42
  • 1
    I cannot find this overload in [cppreference](https://en.cppreference.com/w/cpp/string/basic_string/basic_string). What is it doing? – Yksisarvinen Aug 30 '19 at 10:45
  • Came to the same conclusion in a somehow funny way: [**Live Demo on coliru**](http://coliru.stacked-crooked.com/a/6d76a6f390449744). Ok, ok, a debugger would've been even simpler - but I got a nice demonstration. ;-) – Scheff's Cat Aug 30 '19 at 10:45
  • 3
    @Yksisarvinen `template< class InputIt > basic_string( InputIt first, InputIt last, const Allocator& alloc = Allocator() );` – rafix07 Aug 30 '19 at 10:45
  • @Yuriy What do you mean by _"the choice of overloading with a vector"_? What are you trying to do? – Lightness Races in Orbit Aug 30 '19 at 10:46
  • Thanks :) It would be good if you included that in the answer and explained what happens when both pointers are the same. – Yksisarvinen Aug 30 '19 at 10:48
  • I initially ran into a problem trying to create two overloads for string and vector(commented in question code). This led to an "ambiguous call" error. So "achieve the choice of overloading with a vector" is a question about using syntax test({"one", "two"}) for vector overload. – Yuriy Aug 30 '19 at 11:36
  • That overload should so be explicit. – Yakk - Adam Nevraumont Aug 30 '19 at 19:50
  • @Scheff Hey look string pooling exploit: http://coliru.stacked-crooked.com/a/8d20450bba0425e0 – Yakk - Adam Nevraumont Aug 30 '19 at 19:53
6

For starters there is no used the constructor that accepts an initializer list because such a constructor looks like

basic_string(initializer_list<charT>, const Allocator& = Allocator());
                              ^^^^^

So the compiler searches another appropriate constructor and it finds such a constructor. It is the constructor

template<class InputIterator>
basic_string(InputIterator begin, InputIterator end, const Allocator& a = Allocator());

That is the expressions "one" and "two" are considered as iterators of the type const char *.

So the function test has undefined behavior.

You could write for example (provided that string literals with the same content are stored as one string literal in memory, which is not guaranteed and depends on the selected compiler options).

#include <iostream>
#include <string>

void test(const std::string &value) { std::cout << "string overload: " << value << std::endl; }

//void test(const std::vector<std::string> &) { std::cout << "vector overload" << std::endl; }

int main()
{
    test({ "one", "one" + 3 });
}

And you will get a valid result.

string overload: one

Pay attention to that this construction

{ "one", "two" }

is not an object of the type std::initializer_list<T>. This construction does not have a type. It is a braced-init-list that is used as an initialzer. Simply the compiler tries at first to use a constructor that have the first parameter of the type std::initializer_list to use with this initializer.

For example if you will use the class std::vector<const char *> then indeed the compiler will use its constructor with std::initializer_list and correspondingly initializes its parameter with this braced-init-list. For example

#include <iostream>
#include <vector>

int main()
{
    std::vector<const char *> v( { "one", "two" } );

    for ( const auto &s : v ) std::cout << s << ' ';
    std::cout << '\n';
}
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335