首页 > 解决方案 > 带有条件的 constexpr 类成员函数内的非 constexpr 变体成员调用编译 - 为什么?

问题描述

#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 函数,只要它们是该类中的成员函数。

我很难理解发生了什么。我的第一反应是说这是一个错误。这个条件不可能比没有条件更 constexpr。但也许这还为时过早。任何人都可以对此有所了解吗?为什么emplace()不是 constexpr 而是(相等类型)赋值?

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

神箭:这里

标签: c++c++17constexpr

解决方案


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

[dcl.constexpr]

5对于既不是默认也不是模板的 constexpr 函数或 constexpr 构造函数,如果不存在参数值,则函数或构造函数的调用可以是核心常量表达式的求值子表达式,或者,对于构造函数,是常量初始值设定项对于某些对象([basic.start.static]),程序格式错误,不需要诊断。[ 例子:

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
};

 —结束示例]

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

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

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

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


推荐阅读