3

Why do I get one element in b instead of two? In a I get one as expected and in c three elements as expected. The one with the two values is somehow a special case.

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

void print(const std::string& name, const std::vector<std::string>& v)
{
    std::cout << name << ' ' << v.size() << '\n';
    for (const auto& str : v) {
        std::cout << str << '\n';
    }
    std::cout << "\n";
}

int main()
{
    std::vector<std::string> a = {{"str1"}};
    std::vector<std::string> b = {{"str1", "str2"}};
    std::vector<std::string> c = {{"str1", "str2", "str3"}};
    print("a", a);
    print("b", b);
    print("c", c);
    return 0;
}

This prints:

a 1
str1

b 1
str1

c 3
str1
str2
str3

I guess it has something to do with this overload of the vector ctor.

template< class InputIt >
vector( InputIt first, InputIt last,
    const Allocator& alloc = Allocator() );

I used clang 9.0.0 with -std=c++17 -O2 -Wall as flags.

What did the compiler do in the case of b? Why did it decide it's an iterator in one case and initializer list in the other cases? Is my sample code well defined or does it have UB?

Allan Ojala
  • 694
  • 10
  • 22
  • 2
    Similar to [Why does the number of elements in a initializer list cause an ambiguous call error?](https://stackoverflow.com/questions/41507602/why-does-the-number-of-elements-in-a-initializer-list-cause-an-ambiguous-call-er/41507731#41507731). – François Andrieux Oct 08 '19 at 14:02
  • Replacing `{{...}}` with `{...}` fixes the problem. – HolyBlackCat Oct 08 '19 at 14:06

1 Answers1

4

Is my sample code well defined or does it have UB?

It does have UB. The constructor that takes the first-last iterator pair presumes that both iterators refer to the same sequence. {{"str1"}, {"str2"}} does not fulfill this requirement, hence UB.

What did the compiler do in the case of b? Why did it decide it's an iterator in one case and initializer list in the other cases?

Recall that a string literal "str1" is of type char const[5]. However, calls to the constructor overload for std::initializer_list work with std::string instances. This requires an implicit conversion - and as the constructor template for two iterators doesn't require such a conversion, it's considered a better match.


You can fix this by forcing direct list-initialization with one pair of braces,

std::vector<std::string> b2 = {"str1", "str2"};

or by manually specifying the desired type at least once:

std::vector<std::string> b3 = {{std::string("str1"), "str2"}};

using namespace std::string_literals;
std::vector<std::string> b4 = {{"str1"s, "str2"s}};
lubgr
  • 37,368
  • 3
  • 66
  • 117