【问题标题】:not-constexpr variable in if constexpr – clang vs. GCCif constexpr - clang 与 GCC 中的非 constexpr 变量
【发布时间】:2018-02-16 12:14:51
【问题描述】:
struct A{
    constexpr operator bool()const{ return true; }
};

int main(){
    auto f = [](auto v){ if constexpr(v){} };
    A a;
    f(a);
}

clang 6 接受代码,GCC 8 拒绝它:

$ g++ -std=c++17 main.cpp
main.cpp: In lambda function:
main.cpp:6:37: error: 'v' is not a constant expression
  auto f = [](auto v){ if constexpr(v){} };
                                     ^

谁是正确的,为什么?

当我按引用取参数时,都拒绝代码:

struct A{
    constexpr operator bool()const{ return true; }
};

int main(){
    auto f = [](auto& v){ if constexpr(v){} };
    constexpr A a;
    f(a);
}

用clang 6编译:

$ clang++ -std=c++17 main.cpp
main.cpp:6:40: error: constexpr if condition is not a constant expression
    auto f = [](auto& v){ if constexpr(v){} };
                                       ^
main.cpp:8:6: note: in instantiation of function template specialization 
    'main()::(anonymous class)::operator()<const A>' requested here
    f(a);
     ^
1 error generated.

当我将参数复制到局部变量时,都接受代码:

struct A{
    constexpr operator bool()const{ return true; }
};

int main(){
    auto f = [](auto v){ auto x = v; if constexpr(x){} };
    A a;
    f(a);
}

编辑:我确信两种编译器都会正确处理第二种和第三种情况。不过我不知道规则是什么。

在第一种情况下,我怀疑 clang 是正确的,因为这种情况类似于第二种情况。我想知道在第一种情况下 clang 或 GCC 是否正确,在第二种情况下哪些规则使非 constexpr 变量 v 无效,在第三种情况下 x 有效。

编辑 2:第一个问题现在很清楚了: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84421

clang 是对的,GCC 7 也接受了代码。该错误将在 GCC 8 的最终版本中修复。

【问题讨论】:

  • 您是在问v 是否是一个常量表达式,或者为什么在需要的地方不能使用非常量表达式?您对此主题进行了哪些研究?
  • 我希望 Clang 是对的。例如,您可以传入一个std::bool_constant,它虽然不是constexpr 本身,但有一个constexpr 转换运算符到bool
  • 看起来像是this的骗子?
  • 是的,看起来很相似,谢谢!

标签: c++ language-lawyer constexpr c++17 if-constexpr


【解决方案1】:

Clang 在所有情况下都是正确的。 [全面披露:我是 Clang 开发人员]

所有情况下的问题都归结为:我们可以在常量表达式中调用v 上的constexpr 成员函数吗?

要回答这个问题,我们需要看一下 [expr.const]p2,它说:

表达式 e 是一个核心常量表达式,除非按照抽象机 (6.8.1) 的规则对 e 的求值将求值以下表达式之一:

  • ...
  • 一个 id-expression 引用引用类型的变量或数据成员,除非引用具有 之前的初始化和
    • 用常量表达式初始化或
    • 它的生命周期从e 的评估开始;
  • ...

其他规则均不禁止您的任何示例。特别是,如果局部变量不是引用类型,您可以在常量表达式中命名局部变量。 (您不允许对它们执行左值到右值的转换——也就是说,读取它们的值——除非它们的值是已知的(例如,因为它们是constexpr),并且不允许您最终引用此类变量的地址,但您可以为它们命名。)

引用类型的实体的规则不同的原因是仅仅命名引用类型的实体会导致引用立即被解析,即使你对结果不做任何事情,解析引用需要知道它绑定到什么。

所以:第一个例子是有效的。 constexpr 成员函数的*this 值绑定到局部变量a。我们不知道那是什么对象并不重要,因为评估并不关心。

第二个例子(v 是引用类型)格式不正确。仅仅命名v 需要将它解析为它所绑定的对象,这不能作为常量表达式评估的一部分来完成,因为我们不知道它最终会被绑定到什么。后面的评估步骤不会使用结果对象并不重要;引用在命名时立即解析。

第三个例子与第一个例子一样有效。值得注意的是,即使您将 v 更改为引用类型,第三个示例仍然有效:

auto f = [](auto &v) { auto x = v; if constexpr (x) {} };
A a;
f(a);

...因为x 又是一个我们可以在常量表达式中命名的局部变量。

【讨论】:

    猜你喜欢
    • 2015-05-04
    • 2019-05-03
    • 2016-07-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-27
    • 2015-07-04
    • 2020-05-11
    相关资源
    最近更新 更多