4

So I was reading section 3.2 (One definition rule section) in the latest working draft of the C++ standard and came across this:

Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program; no diagnostic required.

This struck me as strange with regard to the variable part, because if a header file has a global constant variable and function like so:

const int k = 10;

int foo(const int &i) {
    return 0;
}

What if you mistakenly call foo(k)? It seems like this would result in UB because k has internal linkage (i.e. every translation unit would have its own definition of k, so there wouldnt be exactly one definition ... in that program). So basically if you have any const variable with global scope you'd have to remember not to call any functions which take its reference or address.

  • Currently the latest working draft is N4527. – Abyx Jul 30 '15 at 20:20
7

The one definition rule in the standard is not "appear" only once, but that they have only a single definition. E.g. if the const were in a header file, it could appear in multiple translation units, yet they would all have the same single definition. As an aside, the linker could then fold them all into a single location (if a location is even required).

What if you mistakenly call foo(k)? It seems like this would result in UB...

There would be no undefined behavior here. The internal linkage is fine, it is const and there is only a single definition of it.

This (the const int i = 1234;) has a general use case for program wide constant values, e.g. const double pi = 3.1415, const std::size_t answer = 42; etc.

On a side note, you have specifically asked about the global variables, but placing constants and the like into namespaces are always a good way to avoid name clashes etc.

  • Can you check the beginning of section 2.2 in this article? This describes my concerns. The relevant quote is: "This function violates the ODR ([basic.def.odr] §3.2/6 ) twice because neither of the constructor 2 arguments receives an lvalue-to-rvalue conversion. They are therefore passed by address, but the address depends on the TU because const (and constexpr) implies internal linkage." – Justin Jul 24 '15 at 19:39
  • There is another paper here that proposes an alternative to the "hole" in the language you present. A key quote from your paper is "There is no way to share a compile-time constant between TUs unless it is scalar and every use is discarded or undergoes lvalue-to-rvalue conversion. This is a major hole in the language." The issue here is to that the authors want a way of defining a single instance of an object (non-scalar) for the program (instead of one for each TU) without it being extern - akin to the inline function. – Niall Jul 24 '15 at 20:09
2

You're close, but not quite. The bad practice here is declaring global variables outside of a namespace.

namespace jucestain {
    const int k = 10;

    int foo(const int &i) {
        return 0;
    }
} // end namespace jucestain

Once you put it in a namespace, foo(k) will only resolve to foo(10) if you have using namespace jucestain; at the top of your file. Otherwise you'd have to write jucestain::foo(jucestain::k), which is pretty hard to do by accident.

And even without a namespace, there's no undefined behavior because k still only has one definition per translation unit, and it's the translation units that matter (otherwise the header file containing k wouldn't have to be "included in multiple places" to begin with).

  • there's no undefined behavior because k still only has one definition per translation unit. The quote from the standard states: that is odr-used in that program. I actually found a paper that mentions this here on section 2.2 page 2. The relevent quote is "This function violates the ODR ([basic.def.odr] §3.2/6 ) twice because neither of the constructor 2 arguments receives an lvalue-to-rvalue conversion. They are therefore passed by address, but the address depends on the TU because const (and constexpr) implies internal linkage." – Justin Jul 24 '15 at 19:31
  • @jucestain: The trick is that each k in the different translation units is considered to be a distinct variable. They just happen to have the same name. This is what it means for a variable/function to have internal linkage. – Bart van Ingen Schenau Jul 25 '15 at 8:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.