当您具有值类型的继承结构时,try_emplace 不起作用(按需要)?

try_emplace doesn't work (as desired) when you have an inheritance structure for the value type?

// 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);

回复几位发贴者:

  1. 我知道如果 B 在地图中存储为 A,它会被切片。这就是为什么我特别提到B中没有额外的数据成员。

  2. 是的,我绝对会尝试确保在插入失败时不会构建 B。我让它编译的唯一方法是构造一个 B 然后将它推到地图中。所以这不是地图的错:)

  3. 所以 B 和 A 之间唯一真正的区别是它们的 ctor。

您不能将 B 放入 A。

可以将B切片成A,切片是指将B的A部分复制到A对象中,

在 C++ 中,变量是它们的类型。它们不是不同的类型,即使其他类型适合存储。

指针和引用可以引用 class 的基础 class 组件。但价值观始终如一。

无法在该映射中存储 B。

现在,如果您可以切片,我们可以推迟 B 的构造,直到 emplace 为它找到一个位置。然后将构建的 B 切片,将 A 部分移入存储,然后销毁。

template<class F>
struct ctor{
  F f;
  template<class T>
  operator T()&&{
    return std::move(f)();
  }
};
template<class F>
ctor(F)->ctor<F>;

现在只是:

auto [_, success] = map.try_emplace(
  5,
  ctor{[&]{return B{5, 6};}}
};

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)

你不能。作为一种解决方法,您可以先检查节点将被插入的位置,然后如果不存在,则将其用作插入元素的提示:

const auto it = map.lower_bound(5);
if (it == map.end() || it->first != 5) {
    map.insert(it, std::make_pair(5, B{5, 6}));
}

使用插入提示可以避免再次沿着树走下去。

让我们稍微简化一下您的示例。

void foo(bool b, int* a) {
    if (b) return;
    for (int i = 0; i < 1000; ++i)
        a[i] = i + 42;
}

foo (rand() == 42, std::array<int, 1000>().data());

如果 foo 无法使用它,您认为编译器会忽略创建 std::array<int, 1000> 吗?

如果您希望仅当 foo 超过第一个 return 语句时才创建大数组,那么只有当 foo 超过时才创建大数组由您决定通过第一个 return 语句。有很多方法可以做到这一点,但希望编译器会神奇地为您完成它不是其中之一。您需要更改 foo 以便它在需要时创建一个大数组。

回到您的示例,您已通过编写 B{5, 6} 请求创建类型为 B 的临时对象,并且您已获得类型为 B 的临时对象,无论 emplace 是否成功。对于您传递给 emplace.

的任何其他对象都是如此
  map.try_emplace(5, 5);         // 5 is always constructed
  map.try_emplace(5, A{5});      // A{5} is always constructed
  map.try_emplace(5, B{5, 6});   // B{5, 6} is always constructed

如果你想只在emplace成功时构造一个B对象,你需要将B的创建移动到emplace时执行的一段代码] 成功。 A 的构造函数是一个很好的地方。

切换到诸如 Haskell 之类的惰性求值语言是该问题的另一种(激进的)解决方案。