Home > Software engineering >  C vector member initialization
C vector member initialization

Time:02-11

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.

  •  Tags:  
  • c
  • Related