In the following example, I have a base and derived struct, each with a single member, and a vector of the derived type. When I try to push_back to the vector, it works as expected. However, I get a compilation error when trying to use emplace_back.
#include <vector>
struct Foo
{
int foo{};
};
struct Bar : public Foo
{
int bar{};
};
int main()
{
std::vector<Bar> barVec;
// Works
barVec.push_back({2, 3});
// Doesn't work
//barVec.emplace_back(2, 3);
return 0;
}
/usr/include/c++/11/bits/alloc_traits.h:518:28: error: no matching function for call to ‘construct_at(Bar*&, int, int)’
518 | std::construct_at(__p, std::forward<_Args>(__args)...);
...
/usr/include/c++/11/bits/stl_construct.h:96:17: error: could not convert ‘std::declval()’ from ‘int’ to ‘Foo’
96 | -> decltype(::new((void*)0) _Tp(std::declval<_Args>()...))
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| |
| int
If I wrap the first argument as a Foo
however, it works.
// Works
barVec.emplace_back(Foo{2}, 3);
I kind of understand what's going on (something about how std::allocator_traits::construct
works) but I'm confused about the specifics. What are the steps that push_back and emplace_back take to construct the object, and why does the original emplace_back fail?
Parenthesized aggregate initialization doesn't support brace elision. (§dcl.init)
Bar b1(2,3);
would fail.
Bar b2(Foo(2),3)
,
Bar b2(Foo{2},3)
,
Bar b2({2},3)
* would success
*since there is no way to pass {2}
(as braced-init-list) around, you cannot use it at vector::emplace_back
either.