Mik*_*ail 0 c++ one-definition-rule constexpr c++20 consteval
我试图探究某个功能的含义,inline并偶然发现了这个问题。考虑这个小程序(demo):
/* ---------- main.cpp ---------- */
void other();
constexpr int get()
{
return 3;
}
int main()
{
std::cout << get() << std::endl;
other();
}
/* ---------- other.cpp ---------- */
constexpr int get()
{
return 4;
}
void other()
{
std::cout << get() << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
在不进行优化的情况下进行编译时,该程序将产生以下输出:
3
3
Run Code Online (Sandbox Code Playgroud)
可能不是我们想要的,但是至少我可以解释一下。
constexpr在编译时计算函数结果,因此决定将其推迟到运行时。constexpr 在功能上意味着 inlineget()功能碰巧有不同的实现get()函数声明为静态get()功能的一种实现碰巧的是,链接器get()从中选择main.cpp,返回了3。
现在到我不了解的部分。我只是将get()功能从更改constexpr为consteval。现在,要求编译器在编译期间(即在链接时间之前)计算值(对吗?)。我希望get()功能根本不会出现在目标文件中。
但是,当我运行它(演示)时,我得到的输出完全相同!怎么可能呢?..是的,我理解这是未定义的行为,但这不是重点。应该在编译时计算出的值如何干扰其他转换单元?
UPD:我知道此功能在clang中未实现,但该问题仍然适用。是否允许合格的编译器表现出这种行为?
对consteval函数的要求是对它的每次调用都必须产生一个常量表达式。
一旦编译器满足了调用确实会产生一个常量表达式的要求,就不要求它不得对函数进行代码生成并在运行时调用它。当然,对于某些consteval功能(例如为反射而设想的功能),最好不要这样做(至少如果它不希望将其所有内部数据结构放入目标文件中),但这不是一般要求。
未定义的行为是未定义的。
具有相同内联函数的两个定义的程序是格式错误的程序,不需要诊断。
该标准对格式错误的程序的运行时或编译时行为没有任何要求。
现在,您正在想象中的C ++中没有“编译时间”。尽管几乎每个C ++实现都编译文件,链接文件,构建二进制文件然后运行它,但C ++标准却围绕这一事实进行了介绍。
它讨论翻译单元,将它们放到程序中时会发生什么,以及该程序的运行时行为是什么。
...
实际上,您的编译器可能正在构建从符号到某些内部结构的映射。它正在编译第一个文件,然后在第二个文件中仍在访问该映射。内联函数的新定义?只是跳过它。
其次,您的代码必须产生一个编译时常数表达式。但是,编译时常数表达式在您使用它的上下文中不是可观察的属性,并且在链接甚至运行时执行它都没有副作用!在没有任何阻止的情况下。
consteval的意思是“如果我运行此命令,并且违反了允许其成为常量表达式的规则,那么我应该出错,而不是依赖非常量表达式”。这类似于 “必须在编译时运行”,但是不一样。
要确定其中发生了什么,请尝试以下操作:
template<auto x>
constexpr std::integral_constant< decltype(x), x > constant = {};
Run Code Online (Sandbox Code Playgroud)
现在将您的打印行替换为:
std::cout << constant<get()> << std::endl;
Run Code Online (Sandbox Code Playgroud)
这使得推迟对运行/链接时间的评估变得不切实际。
这将区分“编译器很聪明和缓存get”与“编译器稍后在链接时评估它”,因为确定ostream& <<要调用的对象需要实例化的类型constant<get()>,而后者又需要评估get()。
编译器倾向于不延迟过载解析来链接时间。