构造函数使用一个函数来获取引用并按值返回,同时重复修改数据成员:
constexpr int vv(int x) {return x;}
constexpr int & rr(int & x) {return x;}
constexpr int rv(int & x) {return x;}
constexpr struct S {
int x {0};
template<typename F> constexpr S(F f) {x = f(x) + 1; x = f(x) + 1;}
} s(rv); // s.x is 1 if function rv is used, 2 otherwise.
static_assert(s.x == 2, "");
只有函数rv
在构造函数中使用时会产生意外结果。如果传递vv
或rr
,则s.x
按预期为2。
我注意到GCC 5.4.1 ARM上的行为,并且它似乎在支持C ++ 14的所有GCC版本中都是相同的。 Clang在所有情况下都给出了2的预期结果。 GCC和Clang都没有给Wall和Wextra启用任何警告。
此示例是否有效C ++ 14和GCC中的错误?我阅读了标准中对常量表达式的限制列表,看不出任何明显的违反,但我不确定我是否理解所有技术细节。
答案 0 :(得分:8)
您的代码当然是为了良好的形式。我相信海湾合作委员会会采用经修订的备忘录形式;回到C ++ 11,对象无法在常量表达式中修改,因此缓存函数结果非常好。实际上,从一些Clang错误报告中可以明显看出GCC确实如此,例如, here:
Clang的constexpr实现不执行缓存。在C ++ 14中, 它甚至不清楚是否可以进行缓存,所以看起来似乎如此 现在浪费时间投资。
他们显然不得不在C ++ 14中引入一些额外的分析,他们犯了一个错误:他们认为任何constexpr
对象的子对象在其生命周期内都无法修改。在封闭物体的构造期间,这显然不成立。如果我们使用临时而不是x
,the code compiles。如果我们将整个内容放入constexpr
函数并删除s {#1}}说明符it works。
有趣的是,constexpr
和rr
的结果也会被缓存,但返回的相同引用工作正常,并且按值调用可以完全避免这个问题: )
报告为79520。