Yakov Dan
Yakov Dan

Reputation: 3347

vector resize seems to require default constructor in C++ >= 11

I have the following test code where I create a std::vector of class foo and later resize it.

#include <iostream>
#include <vector>
#include <algorithm>

class foo {
public:
    foo(int n) : num(n) {}
    //foo() : num(42) {}
    ~foo () { std::cout << "destructor" << std::endl; }
    void print() { std::cout << num << std::endl; }
protected:
    int num;
};


int main()
{
    std::vector<foo> v = { 3,2,1,7,5,1,8,9 };
    v.resize(2);
        
    for (auto obj: v)
        obj.print();

    return 0;
}

When the default constructor is commented out, I get the the compilation error:

Error C2512 'foo::foo': no appropriate default constructor available

I compile using Visual Studio 2019, language is set to C++ 17. I don't understand this error message because in C++ >= 11, std::vector::resize has an the following overload:

void resize (size_type n)

so I'm supposed to be able to call it without a default value, or without there being a default constructor defined. What's going on?

Upvotes: 3

Views: 1727

Answers (4)

Ted Lyngmo
Ted Lyngmo

Reputation: 117298

Yes and no.

The overload that you are currently using does. There is however a second overload where you can supply a value for the new elements - which is needed even if you know that you'll be resizing it to a smaller size.

constexpr void resize( size_type count, const value_type& value );

So either:

v.resize(2, 0);

or erase the elements you do not want to keep. That way, you do not need to supply a value.

v.erase(std::next(v.begin(), 2), v.end());

If you want to use resize() as the main method of resizing you could add a helper function that only uses erase() as a fallback for types that are not default constructible.

#include <type_traits>

template <typename C>
void downsize(C& c, std::size_t size) {
    if (size < c.size()) {
        if constexpr (std::is_default_constructible_v<typename C::value_type>) {
            c.resize(size);
        } else {
            c.erase(std::next(c.begin(), size), c.end());
        }
    }
}

Upvotes: 4

463035818_is_not_an_ai
463035818_is_not_an_ai

Reputation: 122458

You cannot see what a function requires by looking at its signature.

 void resize(size_type n)

This does not let you specify the elements to be inserted, but somehow the elements to be inserted need to be constructed. From cppreference:

Resizes the container to contain count elements.

If the current size is greater than count, the container is reduced to its first count elements.

If the current size is less than count,

  1. additional default-inserted elements are appended

(1 refers to the above overload)

And "default-inserted" requires that the elements are DefaultInsertable:

Specifies that an instance of the type can be default-constructed in-place by a given allocator.

The link has more details on the formal requirements, but the important part is that elements that are added on resize are default-constructed. If your elements cannot be default-constructed, you cannot use the above overload. (Though you can use the other overload, because it copies the element you pass, ie no default construction required)


PS

A default constructor is one that can be called without arguments. Hence you need not add an additional constructor, but this would work as well:

foo(int n = 42) : num(n) {}

However, I advise you to not make foo default-constructible just because some methods of std::vector need it. Rather you should consider if it makes sense to construct a "default foo". Can a foo make sure it is in a valid state after being constructed, when it didn't get any parameters for the construction? Often this is the case, but sometimes not and then it is better to not provide a default constructor, rather than falling back to a create->init antipattern.

Upvotes: 3

eerorika
eerorika

Reputation: 238351

so I'm supposed to be able to call it without a default value

Yes, you can call the single argument overload resize if the element type is default constructible.

or without there being a default constructor defined

The element type doesn't need to be default constructible if you call the two argument overload resize instead.

You cannot call the single argument overload of resize for vectors whose element type is not default constructible.

What's going on?

You call call the single argument overload of resize for a vector whose element type is not default constructible.

Upvotes: 0

Alan Birtles
Alan Birtles

Reputation: 36389

The compiler can't know at compile time that you will only ever use resize with a number smaller than the current size of the vector so it has to compile the path with the new size being larger than the old size and therefore requires the default constructor to create new elements.

If you just want to delete the elements use erase instead:

int main()
{
    std::vector<foo> v = { 3,2,1,7,5,1,8,9 };
    v.erase(v.begin() + 2, v.end());
        
    for (auto obj: v)
        obj.print();

    return 0;
}

Unless you are deliberately copying each element auto obj should probably be auto& obj

Upvotes: 0

Related Questions