constexpr 函数在多个模块中共享

发布于 2025-01-11 07:04:56 字数 1501 浏览 3 评论 0原文

当我使用 constexpr 函数时,我注意到一个奇怪的行为。 我将代码简化为一个简化的示例。 从两个不同的翻译单元(模块 A 和 B)调用两个函数。

#include <iostream>

int mod_a();
int mod_b();

int main()
{
    std::cout << "mod_a(): " << mod_a() << "\n";
    std::cout << "mod_b(): " << mod_b() << "\n";
    std::cout << std::endl;
    return 0;
}

这些模块看起来很相似。这是 mod_a.cpp:

constexpr int X = 3;
constexpr int Y = 4;

#include "common.h"

int mod_a()
{
    return get_product();
}

只有一些内部常量不同。这是 mod_b.cpp:

constexpr int X = 6;
constexpr int Y = 7;

#include "common.h"

int mod_b()
{
    return get_product();
}

两个模块都使用在“common.h”中定义的通用 constexpr 函数:

/* static */ constexpr int get_product()
{
    return X * Y;
}

我非常惊讶这两个函数都返回 12。由于 #include 指令(应该只是包含一些源代码),我认为两个模块之间没有交互。 当我将 get_product 也定义为 static 时,行为符合预期: mod_a() 返回 12, mod_b() 返回 42。

我还查看了 Jason Turner 的 C++ Weekly 第 312 集:停止使用“constexpr”(改为使用此!),网址为 https://www.youtube.com/watch?v=4pKtPWcl1Go

一般使用static constexpr的建议是一个很好的提示。

但我仍然想知道我在没有 static 关键字的情况下注意到的行为是否定义良好。还是UB?或者这是一个编译器错误?

我还尝试了 C 风格的宏 #define get_product() (X*Y),而不是 constexpr 函数,它也向我展示了预期的结果(12 和 42)。

照顾好

迈克尔

I noticed a strange behavior when I was working with a constexpr function.
I reduced the code to a simplified example.
Two functions are called from two different translation units (module A and B).

#include <iostream>

int mod_a();
int mod_b();

int main()
{
    std::cout << "mod_a(): " << mod_a() << "\n";
    std::cout << "mod_b(): " << mod_b() << "\n";
    std::cout << std::endl;
    return 0;
}

The modules look similar. This is mod_a.cpp:

constexpr int X = 3;
constexpr int Y = 4;

#include "common.h"

int mod_a()
{
    return get_product();
}

Only some internal constants differ. This is mod_b.cpp:

constexpr int X = 6;
constexpr int Y = 7;

#include "common.h"

int mod_b()
{
    return get_product();
}

Both modules use a common constexpr function which is defined in "common.h":

/* static */ constexpr int get_product()
{
    return X * Y;
}

I was very astonished that both functions return 12. Due to the #include directive (which should only be some source code inclusion), I supposed that there is no interaction between both modules.
When I defined get_product also to be static, the behavior was as expected:
mod_a() returned 12,
mod_b() returned 42.

I also looked Jason Turner's episode 312 of C++ Weekly: Stop Using 'constexpr' (And Use This Instead!) at https://www.youtube.com/watch?v=4pKtPWcl1Go.

The advice to use generally static constexpr is a good hint.

But I still wonder if the behavior which I noticed without the static keyword is well defined. Or is it UB? Or is it a compiler bug?

Instead of the constexpr function I also tried a C-style macro #define get_product() (X*Y) which showed me also the expected results (12 and 42).

Take care

michaeL

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

淡淡的优雅 2025-01-18 07:04:56

该程序格式错误:XY 具有内部链接,因为它们是命名空间范围内的 const 变量。这意味着 constexpr int get_product() 的两个定义(隐式内联)都违反了 一个定义规则

程序中可以对以下各项进行多个定义:[...]、内联函数、[...],只要满足以下所有条件:

  • [...]
  • 每个定义中的名称查找都会找到相同的实体(在重载解析之后),除了
    • 具有内部链接或无链接的常量可以引用不同的对象,只要它们不被 ODR 使用并且在每个定义中具有相同的值

并且显然这些常量具有不同的值。


发生的情况是 mod_amod_b 在运行时调用 get_productget_product 是隐式内联的,因此选择其中一个定义并丢弃另一个。 gcc 似乎所做的是采用找到的第一个定义:

$ g++ mod_a.cpp mod_b.cpp main.cpp && ./a.out
mod_a(): 12
mod_b(): 12
$ g++ mod_b.cpp mod_a.cpp main.cpp && ./a.out
mod_a(): 42
mod_b(): 42
$ g++ -c mod_a.cpp
$ g++ -c mod_b.cpp
$ g++ mod_a.o mod_b.o main.cpp && ./a.out
mod_a(): 12
mod_b(): 12
$ g++ mod_b.o mod_a.o main.cpp && ./a.out
mod_a(): 42
mod_b(): 42

就好像 get_product 不是 constexpr 一样,因为它是在运行时调用的。

但是,如果您要启用优化(或强制在编译时调用 get_product(),例如使用 constexpr int result = get_product(); return result;),则结果正如您所“期望的”:(

$ g++ -O1 mod_a.cpp mod_b.cpp main.cpp && ./a.out
mod_a(): 12
mod_b(): 42

尽管这仍然是 UB,正确的解决方法是使函数静态

This program ill-formed: X and Y have internal linkage since they are const variables at namespace scope. This means that both definitions of constexpr int get_product() (which is implicitly inline) violate the one definition rule:

There can be more than one definition in a program of each of the following: [...], inline function, [...], as long as all the following is true:

  • [...]
  • name lookup from within each definition finds the same entities (after overload-resolution), except that
    • constants with internal or no linkage may refer to different objects as long as they are not odr-used and have the same values in every definition

And obviously these constants have different values.


What's happening is both mod_a and mod_b are calling get_product at runtime. get_product is implicitly inline, so one of the definitions is chosen and the other is discarded. What gcc seems to do is take the first definition found:

$ g++ mod_a.cpp mod_b.cpp main.cpp && ./a.out
mod_a(): 12
mod_b(): 12
$ g++ mod_b.cpp mod_a.cpp main.cpp && ./a.out
mod_a(): 42
mod_b(): 42
$ g++ -c mod_a.cpp
$ g++ -c mod_b.cpp
$ g++ mod_a.o mod_b.o main.cpp && ./a.out
mod_a(): 12
mod_b(): 12
$ g++ mod_b.o mod_a.o main.cpp && ./a.out
mod_a(): 42
mod_b(): 42

It's as if get_product isn't constexpr, since it is getting called at runtime.

But if you were to enable optimisations (or force get_product() to be called at compile time, like with constexpr int result = get_product(); return result;), the results are as you would "expect":

$ g++ -O1 mod_a.cpp mod_b.cpp main.cpp && ./a.out
mod_a(): 12
mod_b(): 42

(Though this is still UB, and the correct fix is to make the functions static)

情归归情 2025-01-18 07:04:56

这段代码违反了单一定义规则(如果我错了,请语言律师纠正我)。

如果我单独编译代码,我会得到您期望的行为:

g++ -O1 -c main.cpp
g++ -O1 -c mod_a.cpp
g++ -O1 -c mod_b.cpp
g++ *.o
./a.out
> mod_a(): 12
> mod_b(): 42

如果我一次编译所有代码或激活链接时优化,UB 就会变得明显。

g++ -O1 *.cpp
./a.out
> mod_a(): 12
> mod_b(): 12

如何解决这个问题

您将它们声明为静态是正确的。更多的 C++-esce 将是匿名命名空间。您还应该将常量声明为静态或将它们放入命名空间中,而不仅仅是函数中。

mod_a.cpp:

namespace {
constexpr int X = 3;
constexpr int Y = 4;
}

#include "common.h"

int mod_a()
{
    return get_product();
}

common.h:

namespace {
constexpr int get_product()
{
    return X * Y;
}
} /* namespace anonymous */

在我看来,更好的是:将 common.h 包含在打开的命名空间中。这使得声明之间的联系更加明显,并且允许您拥有多个公共 get_products,每个命名空间一个。像这样的东西:

mod_a.cpp:


namespace {
constexpr int X = 3;
constexpr int Y = 4;

#include "common.h"
} /* namespace anonymous */

int mod_a()
{
    return get_product();
}

This code violates the One Definition Rule (language lawyers please correct me if I'm wrong).

If I compile the code separately, I get the behavior that you expect:

g++ -O1 -c main.cpp
g++ -O1 -c mod_a.cpp
g++ -O1 -c mod_b.cpp
g++ *.o
./a.out
> mod_a(): 12
> mod_b(): 42

If I compile all at once or activate link-time optimization, the UB becomes apparent.

g++ -O1 *.cpp
./a.out
> mod_a(): 12
> mod_b(): 12

How to fix this

You are on the right track with declaring them static. More C++-esce would be an anonymous namespace. You should also declare the constants static or put them in a namespace, not just the function.

mod_a.cpp:

namespace {
constexpr int X = 3;
constexpr int Y = 4;
}

#include "common.h"

int mod_a()
{
    return get_product();
}

common.h:

namespace {
constexpr int get_product()
{
    return X * Y;
}
} /* namespace anonymous */

Even better, in my opinion: Include the common.h within an opened namespace. That makes the connection between the declarations more apparent and would allow you to have multiple public get_products, one per namespace. Something like this:

mod_a.cpp:


namespace {
constexpr int X = 3;
constexpr int Y = 4;

#include "common.h"
} /* namespace anonymous */

int mod_a()
{
    return get_product();
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文