C++ 来自不同翻译单元的consteval函数会相互干扰吗?

C++ 来自不同翻译单元的consteval函数会相互干扰吗?,c++,constexpr,c++20,one-definition-rule,consteval,C++,Constexpr,C++20,One Definition Rule,Consteval,我试图深入研究函数内联的含义,并偶然发现了这个问题。考虑这个小程序: 这可能不是我们想要的,但至少我可以解释 编译器不需要在编译时计算constexpr函数的结果,因此它决定将其推迟到运行时。 函数上的constexpr意味着内联 我们的get函数碰巧有不同的实现 我们没有声明get函数是静态的 链接器只能选择get函数的一个实现 碰巧链接器选择了get from main.cpp,它返回了3 现在来谈谈我不明白的部分。我只是将get函数从constexpr改为consteval。现在编译器需要

我试图深入研究函数内联的含义,并偶然发现了这个问题。考虑这个小程序:

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

编译器不需要在编译时计算constexpr函数的结果,因此它决定将其推迟到运行时。 函数上的constexpr意味着内联 我们的get函数碰巧有不同的实现 我们没有声明get函数是静态的 链接器只能选择get函数的一个实现 碰巧链接器选择了get from main.cpp,它返回了3

现在来谈谈我不明白的部分。我只是将get函数从constexpr改为consteval。现在编译器需要在编译时计算值,即在链接时间之前,对吗?。我希望get函数根本不存在于对象文件中

但是当我运行它的时候,我有完全相同的输出!这怎么可能?。。我的意思是,是的,我理解这是未定义的行为,但这不是重点。为什么应该在编译时计算的值会干扰其他翻译单元

UPD:我知道这个功能,但这个问题无论如何都是适用的。是否允许一致编译器表现出这种行为?

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

一旦编译器确信一个调用确实产生了一个常量表达式,就不需要它对函数进行编码并在运行时调用它。当然,对于一些consteval函数,比如那些设想用于反射的函数,最好不要这样做,至少如果它不想将其所有内部数据结构放入对象文件中,但这不是一般要求


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

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

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

现在,C++中没有编译时间,因为你正在想象它。虽然几乎所有的C++实现都编译文件,链接它们,构建二进制,然后运行它,C++标准的尖脚趾围绕这个事实。 它讨论翻译单元,以及将它们组合到程序中时会发生什么,以及该程序的运行时行为

实际上,编译器可能正在构建从符号到某些内部结构的映射。它正在编译您的第一个文件,然后在第二个文件中仍在访问该地图。同一内联函数的新定义?跳过它

其次,代码必须生成编译时常量表达式。但是编译时常量表达式在您使用它的上下文中不是一个可观察的属性,并且在链接时甚至运行时这样做没有副作用!就好像没有什么可以阻止那样

consteval是说,如果我运行它,并且违反了允许它成为常量表达式的规则,我应该出错,而不是依赖于非常量表达式。这类似于它必须在编译时运行,但不同

要确定发生了哪种情况,请尝试以下操作:

template<auto x>
constexpr std::integral_constant< decltype(x), x > constant = {};
现在将打印行替换为:

std::cout << constant<get()> << std::endl;
这使得将评估推迟到运行/链接时间变得不切实际


这将区分编译器是否聪明,以及缓存从编译器获取的数据是否稍后在链接时对其进行评估,因为确定哪个ostream&答案是,无论函数是constexpr还是consteval,它仍然是ODR冲突。也许使用特定的编译器和特定的代码,您可能会得到您期望的答案,但它仍然是格式错误的,不需要诊断

您可以在匿名名称空间中定义它们:

/* ---------- main.cpp ---------- */
void other();

namespace {
    constexpr int get()
    {
        return 3;
    }
}

int main() 
{
    std::cout << get() << std::endl;
    other();
}

/* ---------- other.cpp ---------- */
namespace {
    constexpr int get()
    {
        return 4;
    }
}

void other()
{
    std::cout << get() << std::endl;
}
但更好的是,只需使用模块:

/* ---------- main.cpp ---------- */
import other;

constexpr int get()
{
    return 3;
}

int main() 
{
    std::cout << get() << std::endl; // print 3
    other();
}

/* ---------- other.cpp ---------- */
export module other;

constexpr int get() // okay, module linkage
{
    return 4;
}

export void other()
{
    std::cout << get() << std::endl; // print 4
}

UB意味着你的程序的任何部分都可能发生任何事情。在尝试推理之前,先去掉任何UB。这是一个很好的解释,但我仍然不相信,对不起。请参见,其中在运行时清楚地调用了consteval f。你想说这是正确的行为吗?我认为这是一个bug,与此类似:请看一看:@Mikhail,Clang列出了未实现的即时函数,正如您在bug报告中看到的。使用未实现的功能的结果不是特别令人信服。@chris这就是为什么我首先要问这个问题,因为我没有可以依赖的实现。请看一下:
/* ---------- main.cpp ---------- */
void other();

namespace {
    constexpr int get()
    {
        return 3;
    }
}

int main() 
{
    std::cout << get() << std::endl;
    other();
}

/* ---------- other.cpp ---------- */
namespace {
    constexpr int get()
    {
        return 4;
    }
}

void other()
{
    std::cout << get() << std::endl;
}
/* ---------- main.cpp ---------- */
import other;

constexpr int get()
{
    return 3;
}

int main() 
{
    std::cout << get() << std::endl; // print 3
    other();
}

/* ---------- other.cpp ---------- */
export module other;

constexpr int get() // okay, module linkage
{
    return 4;
}

export void other()
{
    std::cout << get() << std::endl; // print 4
}