constexpr、consteval 和 constinit 是定义而不是关键字就足够了吗?
Would it be sufficient for constexpr, consteval, and constinit to be definitions instead of keywords?
似乎编译时关键字的规则: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;
}
在此处查看:https://godbolt.org/z/KrzGfnEo7
请注意,init1/foo1 完全由编译时关键字指定,不需要保护变量,因为它已完全初始化(不仅仅是 0 填充)。
问题是为什么编译器在 init2/foo2 或 x3/foo3 的情况下不做同样的事情?或者更准确地说,为什么不允许编译器在编译时完全初始化。
编辑(根据评论)请参阅 (Why do we need to mark functions as constexpr?) constexpr
将是必需的。我更关心 consteval
和 constinit
的情况。是否可以修改规范以要求在编译时尝试解析代码并且不会因为缺少 constexpr
而失败?这将允许接口合同根据相关问题仅使用 constexpr
。
Or, at a minimum, if a compile-time keyword is applied to a function and the code would have qualified with had the correct compile-time keywords been applied.
你的问题的基础是假设这些关键字只是一个主题的变体,一个可以包含其中一些关键字的函数 应该 有一个基于单独函数内的属性。
这不合理。
对于函数,任何可以是 constexpr
的函数也可以是 consteval
。区别在于,一个可以在常量表达式求值时被调用,而另一个必须被调用只能在常量表达式评估期间。这不是函数定义的固有 属性;它是关于如何使用.
函数的
编译器不应仅仅因为函数定义恰好对于 consteval
.
来说也是合法的,就推翻程序员能够在运行时使用函数的决定
还应注意,consteval
函数的函数定义要求与 constexpr
函数相同。
也无法知道函数定义是否应该是 constexpr
。请记住:虽然 constexpr
函数 explicitly forbids certain constructs from appearing in the definition, it also permits you to have certain constructs in the definition that aren't allowed to be evaluated during constant evaluation... 只要您实际上从未允许在不断评估期间评估这些代码路径。所以有许多函数定义恰好对 constexpr
有效,即使程序员不打算在编译时对它们进行求值。
Lambda 获得隐式 constexpr
定义只是因为 lambda 表达式中的 space 非常重要。
对于变量,隐式 constexpr
更没有意义。如果您打算稍后在常量表达式中使用变量,则该变量应该是 constexpr
。这要求它有一个作为常量表达式的初始化器。隐式 constexpr
是一个问题,因为您可能开始依赖隐式 constexpr
规则,然后将初始化器更改为不再是常量表达式,并在您使用其 [=10 的地方出现奇怪的错误=]属性。
最好让用户预先明确,而不是误判用户正在尝试做的事情,并让用户认为某事本不该是有效的。
而 constinit
只是为了确保用户永远不会意外使用非常量表达式来初始化变量。使其隐式化绝对没有意义,因为将表达式更改为不再是常量表达式不会导致编译失败。编译器足够聪明,可以知道何时使用常量表达式初始化变量,并可以自行决定如何处理它。
似乎编译时关键字的规则: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;
}
在此处查看:https://godbolt.org/z/KrzGfnEo7
请注意,init1/foo1 完全由编译时关键字指定,不需要保护变量,因为它已完全初始化(不仅仅是 0 填充)。
问题是为什么编译器在 init2/foo2 或 x3/foo3 的情况下不做同样的事情?或者更准确地说,为什么不允许编译器在编译时完全初始化。
编辑(根据评论)请参阅 (Why do we need to mark functions as constexpr?) constexpr
将是必需的。我更关心 consteval
和 constinit
的情况。是否可以修改规范以要求在编译时尝试解析代码并且不会因为缺少 constexpr
而失败?这将允许接口合同根据相关问题仅使用 constexpr
。
Or, at a minimum, if a compile-time keyword is applied to a function and the code would have qualified with had the correct compile-time keywords been applied.
你的问题的基础是假设这些关键字只是一个主题的变体,一个可以包含其中一些关键字的函数 应该 有一个基于单独函数内的属性。
这不合理。
对于函数,任何可以是 constexpr
的函数也可以是 consteval
。区别在于,一个可以在常量表达式求值时被调用,而另一个必须被调用只能在常量表达式评估期间。这不是函数定义的固有 属性;它是关于如何使用.
编译器不应仅仅因为函数定义恰好对于 consteval
.
还应注意,consteval
函数的函数定义要求与 constexpr
函数相同。
也无法知道函数定义是否应该是 constexpr
。请记住:虽然 constexpr
函数 explicitly forbids certain constructs from appearing in the definition, it also permits you to have certain constructs in the definition that aren't allowed to be evaluated during constant evaluation... 只要您实际上从未允许在不断评估期间评估这些代码路径。所以有许多函数定义恰好对 constexpr
有效,即使程序员不打算在编译时对它们进行求值。
Lambda 获得隐式 constexpr
定义只是因为 lambda 表达式中的 space 非常重要。
对于变量,隐式 constexpr
更没有意义。如果您打算稍后在常量表达式中使用变量,则该变量应该是 constexpr
。这要求它有一个作为常量表达式的初始化器。隐式 constexpr
是一个问题,因为您可能开始依赖隐式 constexpr
规则,然后将初始化器更改为不再是常量表达式,并在您使用其 [=10 的地方出现奇怪的错误=]属性。
最好让用户预先明确,而不是误判用户正在尝试做的事情,并让用户认为某事本不该是有效的。
而 constinit
只是为了确保用户永远不会意外使用非常量表达式来初始化变量。使其隐式化绝对没有意义,因为将表达式更改为不再是常量表达式不会导致编译失败。编译器足够聪明,可以知道何时使用常量表达式初始化变量,并可以自行决定如何处理它。