c++c++20constexprlinkageconsteval

What effect does consteval have on linkage in C++?


I have been looking at how different specifiers affect linkage but need some help understanding how consteval affects linkage.

The working draft of the standard says consteval implies inline (source):

"A function or static data member declared with the constexpr or consteval specifier on its first declaration is implicitly an inline function or variable ([[dcl.inline]])"

Which would suggest external weak linkage, meaning we can violate the ODR and the linker would just pick one definition and assume they are all the same (source):

"[Note 3: An inline function or variable with external or module linkage can be defined in multiple translation units ([basic.def.odr]), but is one entity with one address. A type or static variable defined in the body of such a function is therefore a single entity. — end note]"

However, when testing this out I notice that the linkage appears to be internal instead:

    // bar.cpp
    #include <iostream>
    
    consteval int get_value() {
        return 43;
    }
    
    void print_value2() {
        std::cout << "Value from static_consteval_test1: " << get_value() << '\n';
    }
    // foo.cpp
    #include <iostream>
    
    consteval int get_value() {
        return 42;
    }
    
    void print_value1() {
        std::cout << "Value from static_consteval_test1: " << get_value() << '\n';
    }
    //main.cpp
    #include <iostream>
  
    void print_value1();
    void print_value2();
    int main() {
        print_value1();
        print_value2();
        return 0;
    }

Output (mvsc, /std:c++20),

// with consteval
Value from static_consteval_test1: 42
Value from static_consteval_test2: 43

implying internal linkage. If I now specify with static instead, I know that the linkage should be internal and get the same result.

If I replace consteval with constexpr, the linkage appears as weak external linkage (just as I would expect from something that implies inline):

// with constexpr
Value from static_consteval_test1: 43
Value from static_consteval_test2: 43

Leading to my question(s):

  1. How does consteval affect linkage of a function?
  2. (Where does it fall in terms of precedence of linkage-specifying keywords when multiple are present?)

The second question I can obviously try out myself, still some validation would be helpful.


Solution

  • If you violate the ODR, your program is ill-formed no diagnostic required. Any result from the compiler is compliant with the standard. It can email your browser history to your contact list. It can take your photos, replace every face with a clown face, and erase the old versions. Or, it can seem to "work" by running different code in difference cpp files.

    These are all valid responses to an ODR violation.

    In practice, this means that the compiler is permitted to ignore your assumptions about how linkage works. It is free to assume that the definition of an inline function it can see is the one and only and universal definition of that inline function. It is free to use the definition of an inline function from a different compilation unit here, ignoring the definition you provide. Either option can be done with no diagnostic, because if they do differ than your program was already ill-formed, and any behavior is legal.

    In your case, I'd guess that the constexpr version wasn't actually run at compile time, because nothing forced it to happen and compilers (especially with no optimization) are lazy.

    To fix that, try using sizeof( char[get_value()] ) in place of get_value(); while logically identical, a non-optimizing compiler will lazily feel forced to calculate the first at compile time, while the second can be done at run time.

    constexpr means "can be run at compile time"; but if run in a context where it could be run either at compile or run time, no guarantees are made. consteval effectively guarantees it must only be run at compile time. Barring significant LTO, this means that the function seen when running consteval is what is visible in the translation unit. For constexpr, if delayed until runtime, it will be the one after inline de-duplication.

    This, however, is a "just so story". The real key to this story is the ODR violation causing arbitrary compiler output being valid, not the fairy tale of how one specific version of a specific compiler practically solves the C++ compilation problem based off my personal heuristics and limited knowledge of how compilation works. That is just a fairy tale.