Reputation: 3347
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
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
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,
- 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
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
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