Gle*_*aum 5 c++ language-lawyer c++20
编译时关键字: 和 的规则似乎constexpr已经consteval定义constinit得足够好,编译器可以在您误用标签时发出警告。
这是有道理的(很像内联)编译器可以在所有地方应用规则来确定代码实际上是否可以应用编译时关键字之一,并且根据语言规范强制编译为尽可能就像应用了编译时关键字一样。
或者,至少,如果将编译时关键字应用于函数,并且如果应用了正确的编译时关键字,则代码将具有资格。编译该函数,就好像所有调用的函数都具有正确的编译时关键字一样。
这是一个简单的例子:
#include <tuple>
consteval std::tuple<int, int> init1(int x, int y)
{
return {x,y};
}
std::tuple<int, int>& foo1()
{
static constinit std::tuple<int, int> x=init1(1,2);
return x;
}
std::tuple<int, int> init2(int x, int y)
{
return {x,y};
}
std::tuple<int, int>& foo2()
{
static std::tuple<int, int> x=init2(1,2);
return x;
}
static std::tuple<int, int> x3=init2(1,2);
std::tuple<int, int>& foo3()
{
return x3;
}
Run Code Online (Sandbox Code Playgroud)
在这里查看: https: //godbolt.org/z/KrzGfnEo7
请注意,init1/foo1 是用编译时关键字完全指定的,并且不需要保护变量,因为它已完全初始化(而不仅仅是填充 0)。
问题是为什么编译器在 init2/foo2 或 x3/foo3 的情况下不做同样的事情?或者更准确地说,为什么不允许编译器在编译时完全初始化。
编辑(根据评论)请参阅(为什么我们需要将函数标记为 constexpr?)constexpr将是必需的。我更关心 和 的consteval案件constinit。是否可以修改规范以要求在编译时尝试解析代码并且不会因为缺少 而失败constexpr?constexpr这将允许接口合约仅根据相关问题使用。
或者,至少,如果将编译时关键字应用于函数,并且如果应用了正确的编译时关键字,则代码将具有资格。
您的问题的基础是假设这些关键字只是主题的变体,可能具有其中一些关键字的函数应该仅基于函数内的属性而具有特定的关键字。
这不合理。
对于函数来说,任何可以是的函数constexpr也可以是consteval。区别在于,一个可以在常量表达式求值期间调用,另一个只能在常量表达式求值期间调用。这不是函数定义的固有属性;这是关于如何使用该功能的。
编译器不应该仅仅因为函数定义恰好对于consteval.
还应该注意的是,函数定义的要求与函数consteval相同。constexpr
也不可能知道函数定义constexpr是否应该。请记住:虽然constexpr函数明确禁止某些构造出现在定义中,但它也允许您在定义中拥有某些不允许在持续求值期间求值的构造......只要您实际上从未允许这些代码路径在不断评估过程中进行评估。因此,有许多函数定义恰好是有效的,constexpr即使程序员不打算在编译时评估它们。
Lambda 获得隐式constexpr定义只是因为 Lambda 表达式中的空间非常宝贵。
对于变量来说,隐式constexpr就更没有意义了。constexpr如果您打算稍后在常量表达式中使用变量,则应该使用变量。这要求它有一个常量表达式的初始值设定项。隐式constexpr是一个问题,因为您可能开始依赖隐式constexpr规则,然后将初始值设定项更改为不再是常量表达式,并在使用其constexpr属性的位置出现奇怪的错误。
最好让用户预先明确,而不是误判用户想要做什么,并让用户认为某些事情应该是有效的,而实际上并非如此。
仅仅constinit是为了确保用户永远不会意外地使用非常量表达式来初始化变量。使其隐式绝对没有意义,因为将表达式更改为不再是常量表达式不会导致编译失败。编译器足够聪明,可以知道变量何时用常量表达式初始化,并可以决定如何自行处理。