gpt4 book ai didi

c++ - std::optional:不参与重载决议与被定义为已删除

转载 作者:行者123 更新时间:2023-12-03 06:50:13 24 4
gpt4 key购买 nike

我试图理解类型特征传播背后的机制,如 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0602r4.html 中 std::optional 所述.复制操作的处理有细微的差别,复制操作应有条件地定义为删除,而移动操作则不应参与重载决议。
这种差异的原因是什么,我将如何测试后者?例子:

#include <type_traits>
#include <optional>

struct NonMoveable {
NonMoveable() = default;
NonMoveable(NonMoveable const&) = default;
NonMoveable(NonMoveable&&) = delete;
NonMoveable& operator=(NonMoveable const&) = default;
NonMoveable& operator=(NonMoveable&&) = delete;
};

// Inner traits as expected
static_assert(!std::is_move_constructible<NonMoveable>::value);
static_assert(!std::is_move_assignable<NonMoveable>::value);

// The wrapper is moveable, via copy operations participating in
// overload resolution. How to verify that the move operations don't?
static_assert(std::is_move_constructible<std::optional<NonMoveable>>::value);
static_assert(std::is_move_assignable<std::optional<NonMoveable>>::value);

int main(int argc, char* argv[])
{
NonMoveable a1;
NonMoveable a2{std::move(a1)}; // Bad, as expected
std::optional<NonMoveable> b1;
std::optional<NonMoveable> b2{std::move(b1)}; // Good, see above. But
// useless as a test for
// P0602R4.
return 0;
}
奖金问题
GCC 做正确的事吗?我稍微修改了示例以更接近一小步: https://godbolt.org/z/br1vx1 .在这里,我通过将它们声明为私有(private)来使复制操作不可访问。带有 -std=c++20 的 GCC-10.2 现在使静态断言失败并提示
error: use of deleted function 'std::optional<NonMoveable>::optional(std::optional<NonMoveable>&&)'
根据 Why do C++11-deleted functions participate in overload resolution?在重载决议之后应用删除,这可能表明移动构造函数参与,尽管 P0602R4 表示不应。
另一方面 https://en.cppreference.com/w/cpp/language/overload_resolution一开始的状态

... If these steps produce more than one candidate function, then overload resolution is performed ...


所以跳过重载决议,因为移动构造函数是唯一的候选者?

最佳答案

std::optional 是一个红鲱鱼;关键是了解导致为什么将这些要求放在库类型上的机制

There is a subtle difference in the treatment of copy operations, which shall be conditionally defined as deleted, versus move operations, which shall rather not participate in overload resolution.

std::optional 的底层要求(以及如何实现这些要求)很复杂。然而,移动操作不应参与重载决议(对于不可移动类型)与被删除的复制操作(对于不可复制类型)的要求可能与一个单独的主题有关;
  • NRVO(1)(复制省略的情况,如果可以的话)和
  • 更多隐式 Action ,例如当从函数返回具有自动存储持续时间的命名对象时,选择移动构造函数而不是复制构造函数。

  • 我们可以通过查看比 std::optional 更简单的类型来理解这个主题。 .
    (1)命名返回值优化

    TLDR
    在 C++ 中扩展的移动渴望(在 C++20 中更多的渴望移动)意味着在某些特殊情况下,即使移动构造函数已被删除,移动构造函数也会被选择而不是复制构造函数。对于不可移动的类型,避免这些的唯一方法是通过了解 5 规则以及决定这些是否隐式定义的因素,确保该类型没有移动构造函数或移动赋值运算符。
    复制不存在相同的偏好,如果可能的话,没有理由支持删除这些而不是删除它们(2)。换句话说,在重载决议中有利于移动的相同扭结,有时意外地选择移动而不是复制,在相反的情况下不存在;复制移动。
    (2) 没有复制构造函数和复制赋值运算符(尽管这些可能被定义为删除),就没有类这样的东西。

    隐式移动渴望
    考虑以下类型:
    struct A {
    A() { std::cout << __PRETTY_FUNCTION__ << "\n"; }
    A(A const &) { std::cout << __PRETTY_FUNCTION__ << "\n"; }
    A &operator=(A const &) {
    std::cout << __PRETTY_FUNCTION__ << "\n";
    return *this;
    }
    };

    struct B {
    B() { std::cout << __PRETTY_FUNCTION__ << "\n"; }
    B(B const &) { std::cout << __PRETTY_FUNCTION__ << "\n"; }
    B &operator=(B const &) {
    std::cout << __PRETTY_FUNCTION__ << "\n";
    return *this;
    }

    B(B &&) = delete;
    B &operator=(B &&) = delete;
    };
    哪里, A :
  • 具有用户定义的构造函数,因此不会隐式定义移动构造函数和移动赋值运算符,

  • 哪里 B , 而且:
  • 声明 并删除移动构造函数和移动赋值运算符;因为这些声明和 定义 作为删除,他们将参与重载决议。

  • 在我们继续讨论不同的标准版本之前,我们定义了以下将返回的函数:
    A getA() {
    A a{};
    return a;
    }

    B getB() {
    B b{};
    return b;
    }
    C++14
    现在,在 C++14 中,允许在某些情况下实现复制(/移动)省略;引用 [class.copy]/31来自 N4140(C++14 + 编辑修复)[ 重点 矿]:

    When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the constructor selected for the copy/move operation and/or the destructor for the object have side effects. [...]

    This elision of copy/move operations, called copy elision, is permitted in the following circumstances:

    • in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv-unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function's return value
    • [...]

    并且,来自 [class.copy]/32 [ 重点 矿]:

    When the criteria for elision of a copy/move operation are met, but not for an exception-declaration, and the object to be copied is designated by an lvalue, or when the expression in a return statement is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.


    但是 [class.temporary]/1仍然对对象的省略拷贝施加相同的语义限制,就好像该拷贝实际上并未被省略 [ ]重点 矿]

    [..] Even when the creation of the temporary object is unevaluated (Clause [expr]) or otherwise avoided ([class.copy]), all the semantic restrictions shall be respected as if the temporary object had been created and later destroyed.


    这样,即使在复制省略是合格(并执行)的情况下,来自,比如说,NRVO 可行的命名对象的转换序列,也需要通过重载决议来找到(可能被省略)转换构造函数,并且将开始通过重载决议,就好像对象是由右值指定的一样。这意味着, 在 C++14 中 , 以下是 结构良好
    auto aa{getA()};  // OK, and copy most likely elided.
    而以下是 格式错误 :
    auto bb{getB()};  // error: use of deleted function 'B::B(B&&)'    
    因为重载决议会找到 B 的声明但已删除的移动构造函数在考虑 b的步骤中在 return b;getB()作为右值。对于 A ,不存在移动构造函数,这意味着 a 的重载决议在 return a;getA()a因为右值会失败,然后没有这个扭结的重载决议会成功找到 A 的复制构造函数(随后将被省略)。
    C++17
    现在,在 C++17 中,通过延迟(完全消除)临时物化的概念,特别是添加 [class.temporary]/3 的概念使复制消除变得更强大。 [ 重点 矿]:

    When an object of class type X is passed to or returned from a function, if each copy constructor, move constructor, and destructor of X is either trivial or deleted, and X has at least one non-deleted copy or move constructor, implementations are permitted to create a temporary object to hold the function parameter or result object. The temporary object is constructed from the function argument or return value, respectively, and the function's parameter or return object is initialized as if by using the non-deleted trivial constructor to copy the temporary (even if that constructor is inaccessible or would not be selected by overload resolution to perform a copy or move of the object).


    这有很大的不同,因为现在可以为 getB() 执行复制省略。不通过返回值重载解析的特殊规则(之前选择了删除的移动构造函数),这样这两个在 C++17 中都是格式良好的:
    auto aa(getA());  // OK, copy elided.
    auto bb(getB()); // OK, copy elided.
    C++20
    C++20 实现 P1825R0甚至允许 more implicit moves ,扩大了可能发生移动构造或分配的情况,即使乍一看,期望复制构造/分配(可能被省略)。

    概括
    关于移动渴望(过度复制)的相当复杂的规则可能会产生一些意想不到的效果,如果设计者想要确保一个类型不会遇到在重载决议中删除的移动构造函数或移动赋值运算符优先的极端情况在未删除的复制构造函数或复制赋值运算符上,与声明它们并将它们显式定义为相比,最好确保没有可用于重载解析的移动构造函数/赋值运算符(对于这些情况) -删除。但是,此参数不适用于移动构造函数/复制赋值运算符,因为:
  • 该标准不包含类似的复制渴望(过度移动),以及
  • 没有复制构造函数或复制赋值运算符这样的类是不存在的,并且从重载解析中删除这些基本上(3) 只能在 C++20 中使用 requires 子句。

  • 作为为非语言律师制定正确规则的困难的一个例子(可能是 GCC 回归错误),GCC 主干目前拒绝以下 C++20 程序( DEMO ):
    // B as above
    B getB() {
    B b{};
    return b;
    }
    带有错误消息
     error: use of deleted function 'B::B(B&&)'

    在这种情况下,在 B 的情况下,人们会期望在上面选择一个拷贝(可能被省略)。已经删除了它的移动 ctor。如有疑问,请确保移动构造函数和赋值运算符不参与(即存在)重载决议。
    (3) 可以声明一个被删除的赋值运算符,同时重载了 const - 和引用限定符,比如 const A& operator=(const A&) const && = delete; ,这在重载解决方案期间很少成为可行的候选者(分配给 const 右值),并且可以保证不存在其他非常量和 & - 限定的重载,否则可能是有效的重载候选者。

    关于c++ - std::optional:不参与重载决议与被定义为已删除,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64153799/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com