4
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 的最终版本中修复。

4

1 回答 1

6

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

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

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

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

  • ...
  • 一个id 表达式,它引用引用类型的变量或数据成员,除非引用具有前面的初始化并且
    • 它用常量表达式初始化或
    • 它的生命周期开始于e;
  • ...

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

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

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

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

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

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

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

于 2018-02-17T01:19:22.800 回答