来自不同翻译单元的约束函数可以干扰吗?

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)

可能不是我们想要的,但是至少我可以解释一下。

  1. 不需要编译器constexpr在编译时计算函数结果,因此决定将其推迟到运行时。
  2. constexpr 在功能上意味着 inline
  3. 我们的get()功能碰巧有不同的实现
  4. 我们没有将get()函数声明为静态
  5. 链接器只能选择该get()功能的一种实现

碰巧的是,链接器get()从中选择main.cpp,返回了3。

现在到我不了解的部分。我只是get()功能从更改constexprconsteval。现在,要求编译器在编译期间(即在链接时间之前)计算值(对吗?)。我希望get()功能根本不会出现在目标文件中。

但是,当我运行它(演示)时,我得到的输出完全相同!怎么可能呢?..是的,我理解这是未定义的行为,但这不是重点。应该在编译时计算出的值如何干扰其他转换单元?

UPD:我知道此功能在clang中未实现,但该问题仍然适用。是否允许合格的编译器表现出这种行为?

T.C*_*.C. 6

consteval函数的要求是对它的每次调用都必须产生一个常量表达式。

一旦编译器满足了调用确实会产生一个常量表达式的要求,就不要求它不得对函数进行代码生成并在运行时调用它。当然,对于某些consteval功能(例如为反射而设想的功能),最好不要这样做(至少如果它不希望将其所有内部数据结构放入目标文件中),但这不是一般要求。

未定义的行为是未定义的。


Yak*_*ont 6

具有相同内联函数的两个定义的程序是格式错误的程序,不需要诊断。

该标准对格式错误的程序的运行时或编译时行为没有任何要求。

现在,您正在想象中的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()

编译器倾向于不延迟过载解析来链接时间。