3

Consider the following code:

class Test {
    public:
        static const int VALUE = 100;
};

std::tuple<int> foo(std::tuple<int> value) {
    return value;
}

int main()
{
    auto t = std::make_tuple(Test::VALUE); // This compiles fine
    auto t1 = foo(std::make_tuple(Test::VALUE)); // undefined reference to `Test::VALUE' linker error
}

According to the other question (What does it mean to "ODR-use" something?) and the answer:

In plain word, odr-used means something(variable or function) is used in a context where the definition of it must be present.

In the first case (variable t) the variable VALUE is not odr-used because only it' value is needed. But in the second case why the code won't compile? If I would pass t instead of passed rvalue(?) the code would compile fine. How the second case differs from the first and why VALUE is odr-used here?

Mati
  • 753
  • 4
  • 20
  • Anyway.. including the specific compiler(s) would be useful to validate [consistent] interpretations in implementations, as is providing a small https://godbolt.org/ example. – user2864740 May 07 '20 at 18:27
  • Can't reproduce, compiles fine with VS2019 (16.5.4), gcc 9.3 and clang 10. – Werner Henze May 07 '20 at 18:29
  • @WernerHenze it compiles fine, but fails at linking with `undefined reference to Test::Value`. – cigien May 07 '20 at 18:33
  • Links fine on VS2019. – Werner Henze May 07 '20 at 18:34
  • @WernerHenze, I see the problem on my computer with g++ 7.4.0 but the problem is not reproducible at https://ideone.com/sMF7yP, which uses g++ 8.3. – R Sahu May 07 '20 at 18:40
  • Hmm, fails on recent [clang and gcc](https://godbolt.org/z/8WLgUY) – cigien May 07 '20 at 18:40
  • Seems newer `gcc` optimizes the call to `Test::VALUE` to just `mov DWORD PTR [rbp-4], 100` even in `-O0`. That's why there is no link error. Making `VALUE` `volatile` makes it call `mov ... _ZN4Test5VALUEE` – KamilCuk May 07 '20 at 18:43

2 Answers2

3

make_tuple takes the argument by const int& (since it's a constant lvalue and it takes a T&& argument), and binding a reference to a value is an ODR-use.

Both cases are ill-formed no diagnostic required. At high optimisation levels of gcc for example, the entire program is optimised out and there are no linker errors, where at no optimisation, both statements give linker errors.

To make it not an ODR use, you can convert it to an rvalue:

    // Various ways to force the lvalue-to-rvalue conversion
    auto t = std::make_tuple(int(Test::VALUE));
    auto t1 = foo(std::make_tuple((void(), Test::VALUE)));
    auto t2 = foo(std::make_tuple(+Test::VALUE));

(So std::make_tuple takes an int&& of a temporary)

Or you can make the definition inline (Easiest with constexpr):

class Test {
    public:
        static constexpr int VALUE = 100;
};
Artyer
  • 31,034
  • 3
  • 47
  • 75
  • I do not follow - no, all your examples you showed also ODR-use `Test::VALUE`. Why does applying comma operator or unary `+` operator doesn't ODR-use a value? Lvalue-to-lvalue is literally the thing that (potentially) ODR-uses a variable. – KamilCuk May 07 '20 at 19:42
  • 2
    @KamilCuk lvalue-to-rvalue that forms a constant expression is not an ODR-use, and `Test::VALUE` is usable in a constant expression because it is an integral type and it's constant initialized (To allow for C's `static const int` constants to be usable in constant expressions) – Artyer May 07 '20 at 20:11
2

In the first case (variable t) the variable VALUE is not odr-used because only it' value is needed.

No. It's value is needed and it is ODR used.

But in the second case why the code won't compile?

Because the compiler you are using is not smart enough to optimize the second snippet of code. For example, both lines compile fine on gcc9.3 with -O1.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111