3

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

1
  • 1
    basically it's a inline variable/function with different definition, which is UB afaict. Commented Mar 1, 2022 at 11:51

2 Answers 2

5

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)

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you very much for your investigations, your good explanations and the hint to the One Definition Rule (ODR). I think I learned a lot of constexpr and linkage.
2

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();
}

1 Comment

Yes, the single namespace make it work like expected. Good idea. Actually, I noticed this behavior when I wrote some experimental code by abbreviating some work. The include file common.h depends on the constexpr definition which is not nice. For a reasonable application I would provide the constants by a parameter to the function.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.