Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nested list (vector of vectors of strings) initialization fails

This code:

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

class MyClass
{
public:
  MyClass(const std::vector<std::vector<std::string>> & v)
  {
    std::cout << "Vector of string vectors size: " << v.size() << "\n";

    for (size_t i = 0; i < v.size(); i++)
      std::cout << "Vector #" << i << " has size " << v[i].size() << "\n";
  }
};

int main()
{
  MyClass({ { "a" } }); // <--- ok
  MyClass({ { "a", "b" } }); // <--- PROBLEM
  MyClass({ { std::string("a"), "b" } }); // <--- ok
  MyClass({ { "a", "b", "c" } }); // <--- ok
  MyClass({ { "a" },{ "c" } }); // <--- ok
  MyClass({ { "a", "b" },{ "c", "d" } }); // <--- ok
}

outputs this (Visual Studio 2017):

Vector of string vectors size: 1
Vector #0 has size 1
Vector of string vectors size: 4
Vector #0 has size 97
Vector #1 has size 0
Vector #2 has size 0
Vector #3 has size 0
Vector of string vectors size: 1
Vector #0 has size 2
Vector of string vectors size: 1
Vector #0 has size 3
Vector of string vectors size: 2
Vector #0 has size 1
Vector #1 has size 1
Vector of string vectors size: 2
Vector #0 has size 2
Vector #1 has size 2

So, it works OK in all cases except in the case where we have a vector of one vector, containing two strings. It also works in the above case if we explicitly construct std::string from one of the string literals. If both are just plain string literals, the compiler seems to get "confused" and constructs a vector of 4 items, the first of which contains 97 strings. Note that 97 is the character code of "a".

I guess my question is, should the compiler interpret this problematic construction as I'd expect, or is this bad code to initialize a nested list like this?

like image 738
Urmas Rahu Avatar asked Apr 23 '18 11:04

Urmas Rahu


People also ask

Is it possible to initialize any vector with an array in C ++?

C++11 also support std::begin and std::end for array, so a vector can also be initialized like static const int arr[] = {10,20,30}; vector<int> vec(begin(arr), end(arr)); .

How do you initialize a std vector?

Initialization of std::vector We initialize an std::vector by either of the following ways. std::vector<int> marks = {50, 45, 47, 65, 80}; std::vector<int> marks { {50, 45, 47, 65, 80} }; We can also assign values to the vector after declaration as shown below.

Are vectors initialized with 0?

A vector can be initialized with all zeros in three principal ways: A) use the initializer_list, B) use the assign() vector member function to an empty vector (or push_back()), or C) use int or float as the template parameter specialization for a vector of initially only default values.


2 Answers

The inner vector in MyClass({ { "a", "b" } }) is creating using range constructor:

template <class InputIterator>
  vector (InputIterator first, InputIterator last, const allocator_type& alloc = allocator_type());

This happens because { "a", "b" } is interpreted not as std::initializer_list<std::string> but as a pair of raw pointers.

like image 82
Valery Kopylov Avatar answered Oct 31 '22 12:10

Valery Kopylov


Stepping into the offending constructor in the debugger reveals that VC++ has picked the vector<vector<int>> constructor that takes two iterators (they are const char*s in this case).
That is, it treats the construction like

std::vector<std::vector<std::string>> {"a", "b"}

This, of course, leads to undefined behaviour since the two pointers don't belong to the same array.

As a side note, g++ compiles both of

std::vector<std::vector<std::string>> as{{"a", "b"}};
std::vector<std::vector<std::string>> bs{"a", "b"};

but crashes miserably on the latter, while the former behaves as expected.

VC++ compiles the double-braced variable construction in the way you would expect, so I suspect (hope) that there's a bug in VC++.

like image 23
molbdnilo Avatar answered Oct 31 '22 14:10

molbdnilo