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?
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)); .
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.
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.
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.
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++.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With