【问题标题】:A constexpr function with delayed initialization of local variables具有延迟初始化局部变量的 constexpr 函数
【发布时间】:2017-01-06 15:44:08
【问题描述】:

我正在尝试编写一个constexpr 形式的函数:

constexpr int foo(bool cond) {
    int a, b, c;
    if (cond) {
        a = 1;
        b = 2;
        c = 3;
    }
    else {
        a = -1;
        b = -2;
        c = -3;
    }

    return a + b + c;
}

然而,编译器抱怨我使用了未初始化的变量,尽管局部变量的最终初始化是有保证的。

我可以重写函数以使用三元运算符,即int a = cond ? 1 : -1; 等,但我不希望这样做。有没有办法让编译器相信局部变量会被初始化?

【问题讨论】:

  • int a = 0, b = 0, b = 0; ? (no more error) (P.S: 错过了编辑窗口)
  • 在 C++ 中,没有“最终初始化”之类的东西。您要么初始化变量,要么不初始化。以后是否分配给他们无关紧要。 (在 C 和编译器警告“可能未初始化”是不同的。)
  • @VittorioRomeo:当您面临以下选择时:(a) 将一个“概念上错误”的分配给 int,它的运行时间成本恰好为零,也没有维护成本,以及 (b)三十行的 lambda/template/tuple 魔术示例,世界上只有三个人能理解……好吧,我知道我的团队中哪一个通过了代码审查。 :)
  • @LightnessRacesinOrbit 该函数不会总是使用常量表达式参数调用,因此对 a、b 和 c 进行不必要的初始化可能会产生运行时成本。诚然,编译器可能会删除它。
  • @LightnessRacesinOrbit:我正在尽力避免这种情况!像 C++17 那样行事是永远不会被使用的魔法并没有帮助:)

标签: c++ constexpr


【解决方案1】:

然而,编译器抱怨我使用了一个未初始化的变量,尽管局部变量的最终初始化是有保证的。

初始化,或者不初始化,没有“最终初始化”。而且,对于constexpr 函数,有一个要求,在 [dcl.constexpr] 中:

constexpr 函数的定义应满足以下要求:[...] 它的 function-body 应为 = delete= defaultcompound-声明不包含[...]非文字类型或静态或线程存储持续时间的变量的定义,或没有 执行初始化

constexpr 函数中不能有未初始化的变量,这就是 abc 适合你的。

那你能做什么?您可以将abc 初始化为零。这绕过了这个要求。或者您可以在if 的每个作用域内初始化abc。或者您可以推迟到另一个 constexpr 函数进行求和:

constexpr int f(int a, int b, int c) { return a+b+c; };

constexpr int foo(bool cond) {    
    if (cond) {
        return f(1,2,3);
    }
    else {
        return f(-1,-2,-3);
    }    
}

有很多方法可以解决这个问题。

【讨论】:

  • 这很棒。即使我确实建议了一个 C 风格的宏来绕过一些限制,它所做的一切,本质上就是选择调用哪个函数以及如何调用。那么为什么不使用包装器constexpr 函数。完美。
  • −1 元组不够
  • -1 jQuery 不够
  • -1 没有足够的 dank memes
  • auto [my_feelings] = []{ return std::make_tuple(hurt_t{}); }();
【解决方案2】:

然而,编译器抱怨我使用了一个未初始化的变量,尽管局部变量的最终初始化是有保证的。

该标准要求初始化constexpr 函数中的所有局部变量。

来自§7.1.5, par. 3([dcl.constexpr])

constexpr 函数的定义应满足以下要求:[...]

其函数体应为= delete= default,或不包含[...]的复合语句

非文字类型或静态或线程存储持续时间或不执行初始化的变量的定义。 [...]

constexpr int uninit() {
    int a;     // error: variable is uninitialized
    return a;
}

在 C++17 中,您可以使用 std::tuplestructured bindingsIIFE (immediately-invoked function expression) 来保留原始结构:

constexpr int foo(bool cond) 
{
    const auto [a, b, c] = [&cond]
    {
        if (cond) 
        {
            return std::tuple(1, 2, 3);
        }
        else
        {
            return std::tuple(-1, -2, -3);
        }    
    }();

    return a + b + c;
}

由于您的条件和分支是微不足道的,一个 三元运算符 就足够了。如果将来您的初始化逻辑变得更复杂,上面的代码 sn-p 可能会对您有所帮助,但下面的代码应该足够好:

constexpr int foo(bool cond) 
{
    const auto [a, b, c] = cond ? std::tuple(1, 2, 3)
                                : std::tuple(-1, -2, -3);

    return a + b + c;
}

在 C++14 中,您可以使用 std::make_tuplestd::get 代替:

constexpr int foo(bool cond) 
{
    const auto abc = cond ? std::make_tuple(1, 2, 3) 
                          : std::make_tuple(-1, -2, -3);

    return std::get<0>(abc) + std::get<1>(abc) + std::get<2>(abc);
}

在 C++11 中,您可以将函数拆分为两个较小的函数:

template <typename TTuple>
constexpr int sum3(const TTuple& abc)
{
    return std::get<0>(abc) + std::get<1>(abc) + std::get<2>(abc);
}

constexpr int foo(bool cond) 
{
    return cond ? sum3(std::make_tuple(1, 2, 3)) 
                : sum3(std::make_tuple(-1, -2, -3));   
}

如果你决定走那条路,Barry's solution 肯定会更好。


以上所有解决方案:

  • 让你的abc变量const,这总是一件好事。

  • 仅对cond 执行一次检查,以便与 OP 中的代码结构非常相似。

【讨论】:

  • 介意解释反对票吗?我的目标是保持原始结构,而不对cond 引入任何额外检查,将const 添加到abc 作为额外好处。
  • 我没有投反对票,但与 this 相比,所有这些都使代码有点难看。
  • 我也没有投反对票,但您实际上并没有解释 OP 的代码有什么问题,而且您的所有解决方案都只是更冗长的方式来做 OP 已经承认的最后一搏解决方法。相反,他们似乎是在从源头上解决问题。
  • @Borgleader:给变量一个无用的“虚拟初始化值”并阻止它们成为const。 @LightnessRacesinOrbit:添加了标准的引用。
  • C++17 代码非常好,可以很好地解决我的问题,但不幸的是我无法访问符合 C++17 的编译器。在我看来,C++14 代码太丑陋了,所有对 std::get 的调用。我对这个答案投了赞成票,但最终我更喜欢 Barry 的解决方案。
【解决方案3】:

@Borgleader 方式就够了:

constexpr int foo(bool cond) {
    int a=0, b=0, c=0;
    if (cond) {
        a = 1;
        b = 2;
        c = 3;
    }
    else {
        a = -1;
        b = -2;
        c = -3;
    }

    return a + b + c;
}

在 C++11 中编译没有错误,并且仅警告 constexpr 函数中的变量声明是 C++14 扩展,并且在 C++14 模式下没有警告(使用 CLang 3.4. 1)

这是干净、易于阅读和编写并且接近原始代码的代码。但毫无疑问,@Barry's solution 更好。

【讨论】:

  • 我不喜欢这个解决方案,因为它引入了一个无用的零“虚拟初始化值”,它只是为了取悦编译器,最重要的是它阻止了@987654323 @、bc 被标记为 const。此外,即使它在 C++11 中编译没有错误,它也不是有效的 C++11。
  • @VittorioRomeo:你是对的,但我真的认为 Borleader 的评论真的值得回答。
  • @VittoriaRomeo 我认为主观上更好的写作方式是int a{} 等。不再是零。
猜你喜欢
  • 1970-01-01
  • 2019-12-14
  • 2012-03-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-10
  • 2021-03-17
  • 2016-09-07
相关资源
最近更新 更多