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
CodePudding user response:
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
)
CodePudding user response:
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();
}