// A couple of simple structs that inherit AND have different ctor arguments
struct A
{
A(int) {}
};
// Note that "B" is a subclass of "A", which is important below
struct B : A
{
B(int, char) : A(0) // The 0 doesn't matter
{}
// Note that B has no data members that "will be chopped"
};
// A simple map... Note the value type is "A"
// There should be no problem putting B's in here though because they are a subclass of A
// And as noted above, B has no additional members that will be chopped
// (
std::unordered_map<int, A> map;
// This works as expected, because we're trying to emplace an object of type "A"
auto [_, success] = map.try_emplace(
5, // the key
6 // the single argument to A's ctor
);
assert(success);
// This compiles BUT:
// 1. It's attempting to overwrite the key used above (5), and so
// the emplace correctly fails
// 2. Uh oh though -- Obviously a "B" is constructed and destructed though
// the emplace fails
auto [_, success] = map.try_emplace(
5,
B{5, 6} // The map knows the value type as "A", which means I only
// have the option of passing A's ctor args, not B's. This doesn't
// do what I want when I'm emplacing a "B"... In fact, this creates
// an rvalue of type "B" (duh) and then destructs it when the emplace fails.
//
// So my question is: How can I pass the arguments for B's ctor
// to a map that expects an "A" (keep in mind B is a subclass of A)
);
assert(!success);
To respond to a few of the posters:
I am aware that B will get sliced if it is stored as an A in the map. That is why I specifically mention that there are no additional data members in B.
Yes, I am absolutely trying to make sure that B does not get constructed if the insert would fail. The only way I could get it to compile was to construct a B then shove it in the map. So that's not the map's fault :)
So the only real difference between B and A is their ctor.
You cannot put a B into an A.
You can slice a B into an A. Slicing refers to copying the A part of B into an A object,
In C++ variables are their type. They are not a different type, even if that other type would fit in the storage.
Pointers and references can refer to the base class component of a class. But values are always what they are.
There is no way to store a B in that map.
Now if you are ok with slicing, we can defer the construction of the B until emplace finds a spot for it. The B constructed will then be sliced and the A part moved into storage, then destroyed.
template<class F>
struct ctor{
F f;
template<class T>
operator T()&&{
return std::move(f)();
}
};
template<class F>
ctor(F)->ctor<F>;
now just:
auto [_, success] = map.try_emplace(
5,
ctor{[&]{return B{5, 6};}}
};