首页 > 解决方案 > C++:将 constexpr lambda 初始化为 constexpr/非 constexpr 变量

问题描述

我正在阅读 Nicolai M. Josuttis 的“C++ 17 The Complete Guide”一书,但无法理解以下示例

auto squared1 = [](auto val) constexpr { // example 1. compile-time lambda calls
  return val * val;
};

以及对它的声明

如果(仅)lambdaconstexpr可以在编译时使用,但squared1可能在运行时初始化,这意味着如果静态初始化顺序很重要(例如,导致静态初始化顺序失败),可能会出现一些问题。

作者建议考虑以下解决方案

constexpr auto squared = [](auto val) constexpr {
  return val * val;
};

这意味着它将以某种方式避免先前的问题。

我不理解第一个示例关于初始化顺序的问题,因此无法理解作者的解决方案如何改进它。您能否解释一下并举例说明第一个示例的缺点?

标签: c++lambdac++17constexpr

解决方案


该语句auto square1 = 初始化一个全局变量,甚至不是常量。这是一个运行时变量,您可以对其进行变异、分配以及可能的其他内容。

你可以很好地初始化这样的变量:

auto returnMeALambda() {
    int capture = rand() % 2;

    return [capture](auto val) {
        return val * val + capture;
    }
}

auto square1 = returnMeALambda();

可以看到, 的代码returnMeALambda严格来说是运行时的,所以square1是在运行时强制初始化的。

这些变量没有在编译时可用的值。编译器很可能在运行时初始化它,即使不是强制的。这在运行时有成本,并且使用静态初始化顺序 fiasco,您可以在初始化之前从技术上使用 lambda,甚至在初始化之前使用另一个全局:

extern int baseval;

auto returnMeALambda() {
    int capture = baseval + rand() % 2;

    return [capture](auto val) {
        return val * val + capture;
    }
}

auto square1 = returnMeALambda();

int baseval = square1(2);

这段代码总是以未定义的行为结束,因为它总是使用未初始化的变量,无论初始化顺序如何。

作者提出的解决方案是将变量初始化为constexpr. 在这种情况下,这会做三件事:

  • 使变量const. 你不能在运行时改变它的值。
  • 现在强制进行常量初始化。的值square1被编码在可执行的二进制代码中。
  • 使变量值在编译时可用。您现在可以在 constexpr 上下文中使用该变量。

第二点是作者寻求作为解决方案的属性。保证该变量不会在运行时初始化,因为该值在编译时可用并保证编译器的持续初始化。


请注意,在 C++20 中,您可以自行应用第二点,而无需使用constinit.

constinit int value = 9;

现在编译器被迫使用常量初始化来初始化值,而且变量在运行时是可变的。鉴于您可以不断地初始化变量,这有效地解决了初始化顺序的失败。


推荐阅读