【问题标题】:Is there any way for constexpr objects to refer/point to other non-static constexpr objects?constexpr 对象有没有办法引用/指向其他非静态 constexpr 对象?
【发布时间】:2015-10-22 11:24:57
【问题描述】:

假设我想在编译时使用某种算法构建一个图,然后计算图中最终有多少节点。这似乎是 constexpr 的理想情况,而不是模板元编程,因为目标是产生值的计算,而不是真正的类型。我有一些可以工作的代码,但这个功能太新了,我担心编译器会很宽松,我可以将部分标准解释为我不能这样做。

#include <iostream>

struct A { int x; constexpr A(int i) noexcept : x{i} {} };
struct B { A& a; constexpr B(A& a) noexcept : a{a} {} };

constexpr int foo() { 
    A a{55};
    B b{a};
    return b.a.x;
}

template<int N>
void output()
{
    std::cout << N << std::endl;
}

int main() {
    // to be absolutely sure compile time eval'd,
    // pass as template arg
    constexpr auto b = foo();
    output<b>();
}

ab 实例都是在编译时创建的,它们具有相同的生命周期,因此这应该是“安全的”。但是a 是一个非静态对象,this part of the standard 似乎说这是不允许的:

一个实体是一个常量表达式的允许结果,如果它是一个 具有不是临时的静态存储持续时间的对象 对象或者是一个临时对象,其值满足上述条件 约束,或者它是一个函数。

那我可以还是不能? GCC and clang are both fine with it.

【问题讨论】:

  • 该代码通常不会有问题,因为在编译时不使用结果。 constexpr 函数没有具有 来生成编译时间常数。当提供编译时常量参数(如果有)时,它可以做到这一点。不过不确定你的情况。但呈现的代码并没有锻炼编译时间常数。
  • @Jarod42:监督,现在改了,结果一样。
  • @Cheersandhth.-Alf:我已将 constexpr 添加到 'b' 的主要声明中以强制它为编译时间。
  • A a{55}; 可能是constexprB b{a}; 不能是(带有clang 错误消息note: reference to 'a' is not a constant expressionDemo

标签: c++ metaprogramming standards c++14 constexpr


【解决方案1】:

是的,您的示例符合要求。

C++14 宽松constexpr 的特别之处在于,常量表达式求值中的中间结果本身不需要是常量表达式。

returnb.a.x 应用左值到右值的转换,因为函数按值返回,而b.a.x 是:

文字类型的非易失性左值,它引用一个非易失性对象,其生命周期在e的评估中开始

(N4527 §5.20/2.7.4)

如果您尝试将(悬空)引用保存到b.a.x,那将是一个问题。根据您的报价,这不是“常量表达式的允许结果”。

【讨论】:

  • 您的回答最有道理,但似乎与其他人相矛盾:p
  • @JosephGarvin 我不认为其他人理解这个问题或意识到您的示例正在工作。
【解决方案2】:

简而言之,您不能在编译时将非静态/临时值作为引用传递。您可以将静态/全局值作为 constexpr 引用传递。但是其他任何东西在编译时根本不可用

constexpr void foo() {
    int a; // run-time value.
    ...
}

一个明显的解决方案是按值传递。它发生在编译时,因此您可能无法获得通常的优化,但它也发生在编译时

http://ideone.com/J7mVj5

#include <iostream>

struct A { int x; constexpr A(int i) noexcept : x{i} {} };
struct B { A a; constexpr B(A a) noexcept : a{a} {} };

constexpr int foo() { 
    B b{55};
    return b.a.x;
}

template<int N>
void output()
{
    std::cout << N << std::endl;
}

int main() {
    // to be absolutely sure compile time eval'd,
    // pass as template arg
    constexpr auto b = foo();
    output<b>();
}

另见http://ideone.com/tw4jzG

【讨论】:

  • 所以土豆拍错了?
【解决方案3】:

在你的例子中:

B b{a};

b 不是 constexpr 变量,草案 C++14 部分 7.1.5 [dcl.constexpr]p5 说:

对象声明中使用的 constexpr 说明符将对象声明为 const。这样的对象应具有 文字类型并且应该被初始化。如果它由构造函数调用初始化,则该调用应为常量表达式(5.19)。否则,或者如果在引用声明中使用了 constexpr 说明符,则每个 fullexpression 出现在其初始化程序中的应该是一个常量表达式。 [注:每次隐式转换 用于转换初始化表达式,并且用于初始化的每个构造函数调用都是 如此饱满的表情。 ——尾注]

不适用,但如果我们稍微修改您的示例:

int main() {
    constexpr auto b = foo();
    A a1(42) ;
    constexpr B b1( a1 ) ;
}

并引入 b1 这是一个 constexpr 变量然后这将不起作用(see it live),clang 说:

error: 'B{a1}' is not a constant expression
     constexpr B b1( a1 ) ;
                        ^

如果我们进一步修改上面的示例如下:

static A a1(42) ;
constexpr B b1( a1 ) ;

它会起作用的。 constexpr 函数可用于 constexpr,但如果它不满足要求,则不会产生常量表达式。

【讨论】:

  • 无法在常量表达式中初始化 static 变量。这将是一个副作用。见§7.1.5/3.4.4
  • @Potatoswatter 我没有说它可以,我的现场示例没有这样做,我在我的代码中添加了更多上下文来澄清这一点。
  • 我看不出这如何解决问题,即我的 sn-p 是否有效。您似乎提出了一个不同的 sn-p ,但事实并非如此。你说b{a} 不是 constexpr,但我的问题是我是否可以在 constexpr 函数中使用它,我不确定它是否相同(也许是?)。
  • @ShafikYaghmour 这个答案中的最后一个 sn-p 几乎永远无法工作,并且它不能出现在 constexpr 函数中。
猜你喜欢
  • 2017-09-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-09
相关资源
最近更新 更多