非 constexpr 变体成员调用在 constexpr class 成员函数内编译,有条件 - 为什么?

Non-constexpr variant member call compiling inside constexpr class member function with condition - why?

#include <variant>

struct S {
    constexpr auto f() -> void {
        // deleting the next line creates an error
        if(std::holds_alternative<int>(m_var))
            m_var.emplace<double>(5.0);
    }
    std::variant<int, double> m_var;
};

int main() {
    return 0;
}

std::variant 有一个非 constexpr 成员函数 emplace()。通常,您不能在 constexpr 函数中使用它。但是,如果您通过在该类型上使用 std::holds_alternative() 的条件包围该调用,则可以。还有其他 constexpr 函数,只要它们是 class.

中的成员函数

我无法理解发生了什么。我的第一反应是说这是一个错误。该条件不可能比根本没有条件更 constexpr。但也许这还为时过早。任何人都可以阐明这一点吗?为什么 emplace() 不是 constexpr 而(等类型)赋值是?

编辑:也许要扩展一点:一种猜测是所涉及变体的构造函数和析构函数可能是非 constexpr,这就是为什么 emplace 等不是。但有趣的是,即使您显式滥用非 constexpr 构造函数,您也可以使用这样的条件将函数编译为 constexpr。这使该论点无效。

神箭:here.

您实际上不需要深入研究 std::variant 就可以对此进行推理。这主要是关于常量表达式如何工作的。 constexpr 函数的定义方式必须允许在常量表达式中求值。对于某些参数我们是否 运行 变成不能出现在常量表达式中的东西并不重要,只要对于其他参数我们获得有效的常量表达式即可。标准中明确提到了这一点,例如

[dcl.constexpr]

5 For a constexpr function or constexpr constructor that is neither defaulted nor a template, if no argument values exist such that an invocation of the function or constructor could be an evaluated subexpression of a core constant expression, or, for a constructor, a constant initializer for some object ([basic.start.static]), the program is ill-formed, no diagnostic required. [ Example:

constexpr int f(bool b)
  { return b ? throw 0 : 0; }           // OK
constexpr int f() { return f(true); }   // ill-formed, no diagnostic required

struct B {
  constexpr B(int x) : i(0) { }         // x is unused
  int i;
};

int global;

struct D : B {
  constexpr D() : B(global) { }         // ill-formed, no diagnostic required
                                        // lvalue-to-rvalue conversion on non-constant global
};

 — end example ]

看看 f(bool) 如何成为一个有效的 constexpr 函数?即使 throw 表达式可能不会在常量表达式中求值,它仍然可以 出现 constexpr 函数中。只要持续评估达不到就没问题。

如果 没有 组参数可以在常量表达式中使用 constexpr 函数,则程序为 ill-formed。这种 ill-formed 程序不需要诊断,因为仅从函数定义中检查此条件通常是很棘手的。然而,它是无效的 C++,即使编译器没有引发错误。但在某些情况下,它可以被检查,因此编译器可能有义务提出诊断。

您的 f 没有 条件属于此类 ill-formed 结构。不管f怎么调用,它的执行都会导致调用emplace,它不能出现在常量表达式中。但它很容易检测到,因此您的编译器会告诉您这是一个问题。

你的第二个版本,带有条件,不再无条件地调用 emplace。现在是有条件的。条件本身依赖于 constexpr 函数,因此它不会立即 ill-formed。一切都取决于函数的参数(包括 this)。所以它不会立即引发错误。