I am confused about the output in the following program about the vec
in Test
. Why it's a vector with size 100 instead of 1? I thought std::vector<T> var{a}
is the same as std::vector<T> var = {a}
.
#include <iostream>
#include <vector>
using namespace std;
struct Value {
int a;
int b;
};
class Test {
public:
std::vector<struct Value> vec{100};
};
class Test2 {
public:
std::vector<int> vec{100};
};
int main()
{
Test test;
std::cout << "test size: " << test.vec.size() << std::endl;
Test2 test2;
std::cout << "test2 size: " << test2.vec.size();
return 0;
}
Output:
test size: 100
test2 size: 1
CodePudding user response:
std::vector
has a constructor with a std::initializer_list<T>
argument. When using an initializer list like {100}
this constructor will always take priority, if it is applicable.
For a std::vector<int>
the initializer {100}
is compatible with std::initializer_list<int>
so that constructor will be used. It will create a vector containing the collection {100}
which is a single int
with the value 100
.
For std::vector<Value>
the initializer {100}
is NOT compatible with a std::initializer_list<Value>
argument. Value
has no converting constructor for turning an int
to a Value
so you cannot construct a std::initializer_list<Value>
from {100}
. You can verify that this conversion is not allowed with this example. The compiler will try to take a lower priority constructor instead, and will use the constructor which initializes 100 default constructed Value
.
If you add a Value::Value(int)
constructor or use {{100}}
as the initializer for Test2
you will find that the std::vector<Value>
will now only contain a single element. In both cases, the initializer list is convertible to std::initializer_list<Value>
and that constructor will now be used.
CodePudding user response:
As you discovered the meaning of {100}
, changes for T == int
.
To answer your question briefly:
The 100
in vector<Value>{100}
cannot be interpreted as a Value
and therefore the size constructor takes precedence.
If you insist, {100}
can be interpreted as Value
, so you may need an extra curly braces, vector<Value>{ {100} }
.
See the illustration here: https://godbolt.org/z/xcMT1oc5z
My advice, avoiding further discussion on legalities is the following:
I think the safe way to keep the meaning is to initialize consistently is to use parenthesis, which forces you to do this, I think:
std::vector<int> vec = std::vector<int>(100);
And in general, for member initialization:
std::vector<T> vec = std::vector<T>(100);
CodePudding user response:
This may not be a totally helpful answer, but I decided to put a breakpoint in the class declaration for vector in the STD library.
Answer
In the definition of a vector, there are 3 ways it handles the assignment. A struct will provide a value-construction, where as an int will be assigned as a sized range construction.
It reads std::vector<struct> vect{100};
as building a vector of length 100, while std::vector<int> vect{100};
is acting similarly vect.insert(v.end(),100);
This is based on the type of object passed in for .
For information's sake, the final option is taking a given value, and assigning it to a number of spots. So if you had 100, "x", it would put "x" into your vector 100 times.
The Journey
What I learned from this is that there's a point where your vector takes a size_type input and a _Valty&& input (which I don't know what that is yet. Will be looking it up later) and provides a construction between 3 different args.
My best guess would be that your struct is filling in for 1-args path and acts as a length declaration, while int as a native type falls into the 2-args path and acts as a value assignment.
The sizeof(Value) may == 0, while the size of an int will be 1.
Edit: I guessed 1 and 2 (or _Count == 0, and Count == 1), however I was wrong about this. It's _Count == 0 and _Count == 2. Which was very interesting.
template <class... _Valty>
_CONSTEXPR20 void _Construct_n(_CRT_GUARDOVERFLOW const size_type _Count, _Valty&&... _Val) {
// Dispatches between the three sized constructions.
// 1-arg -> value-construction, e.g. vector(5)
// 2-arg -> fill, e.g. vector(5, "meow")
// 3-arg -> sized range construction, e.g. vector{"Hello", "Fluffy", "World"}
auto& _Al = _Getal(); //////////////// For test1, _Count is 100, for test2, _Count is 1;
auto&& _Alproxy = _GET_PROXY_ALLOCATOR(_Alty, _Al);
auto& _My_data = _Mypair._Myval2;
_Container_proxy_ptr<_Alty> _Proxy(_Alproxy, _My_data);
if (_Count != 0) {
_Buy_nonzero(_Count);
_Tidy_guard<vector> _Guard{this};
// This one happens with a struct
if constexpr (sizeof...(_Val) == 0) {
_My_data._Mylast = _Uninitialized_value_construct_n(_My_data._Myfirst, _Count, _Al);
} else
if constexpr (sizeof...(_Val) == 1) {
_STL_INTERNAL_STATIC_ASSERT(is_same_v<_Valty..., const _Ty&>);
_My_data._Mylast = _Uninitialized_fill_n(_My_data._Myfirst, _Count, _Val..., _Al);
} else
// This one happens with an int
if constexpr (sizeof...(_Val) == 2) {
_My_data._Mylast = _Uninitialized_copy(_STD forward<_Valty>(_Val)..., _My_data._Myfirst, _Al);
} else {
static_assert(_Always_false<_Ty>, "Should be unreachable");
}
_Guard._Target = nullptr;
}
_Proxy._Release();
}
What's really interesting as that it appears to be happening when the Allocator reference is assigned. I'm by no means an expert in the language, but I really wanted to figure this puzzle one! Thanks for the interesting challenge!
If you've never walked through a class definition before, I would recommend trying it out.