4

Here is a problem the reason of which is quite obscure to me, but the workaround of which is fortunately quite easy.

Consider the following code (let me call it my main.cpp):

#include <algorithm>

struct Foo {
    static constexpr float BAR = .42;

    float operator()() const noexcept {
        float zero = .0;
        return std::min(zero, BAR);
    }
};

int main() {
    Foo foo;
    foo();
}

When I tried to compile it, I got the error:

foobar:~/stackoverflow$ g++ -std=c++11 main.cpp
/tmp/ccjULTPy.o: In function 'Foo::operator()() const':
main.cpp:(.text._ZNK3FooclEv[_ZNK3FooclEv]+0x1a): undefined reference to `Foo::BAR'
collect2: error: ld returned 1 exit status

The same happens (quite obviously) also if I use the following statement:

return std::min(zero, Foo::BAR);

Below a slightly modified version of the example above.
This one compiles with no error, even though I'm still referring to the BAR member:

#include <algorithm>

struct Foo {
    static constexpr float BAR = .42;

    float operator()() const noexcept {
        float zero = .0;
        float bar = BAR;
        return std::min(zero, bar);
    }
};

int main() {
    Foo foo;
    foo();
}

I didn't succeed in understanding why the latter version compiles fine while the former ends with an error.
As far as I know, both the versions are correct and should compile, but I strongly suspect that I'm missing something important here.

Any suggestion?

Here my compiler's version: g++ (Debian 5.3.1-5) 5.3.1 20160101.

skypjack
  • 49,335
  • 19
  • 95
  • 187
  • 1
    I [cannot reproduce](http://goo.gl/WdiBJu) – NathanOliver Jan 06 '16 at 16:43
  • 1
    `std::min`, taking arguments by reference, ODR-uses the static member, you need a corresponding definition – Piotr Skotnicki Jan 06 '16 at 16:43
  • Interesting, three comments, three different approaches. Nathan, it is not the compiler I'm using, anyway it's interesting. Piotr, I don't fully get the problem from your point of view, can you be more detailed? R.Sahu: issue with floating point? Actually, I got the error also in real code while dealing with float variables... – skypjack Jan 06 '16 at 16:49
  • @skypjack you only declare a static constexpr member, you don't define it. however, to pass it to `std::min` it has to have an address (as it's taken by reference). if you only assign its value to a local variable, then it's an lvalue to rvalue conversion which doesn't ODR-use the static data member, hence it works – Piotr Skotnicki Jan 06 '16 at 16:51
  • 1
    Related: http://stackoverflow.com/q/29397864/3093378 – vsoftco Jan 06 '16 at 16:52
  • @PiotrSkotnicki It makes sense, but what is curious is that actually it compiles if you look at the link in the comment of NathanOliver, so I guess we are missing something in this reasoning. Am I wrong? – skypjack Jan 06 '16 at 16:54
  • @skypjack I believe violation of this rule doesn't require a diagnostic – Piotr Skotnicki Jan 06 '16 at 16:54
  • The constexpr means evaluate at comnpile time. It does not actually create a Foo::BAR constant. When your second code is made, you are evaluating a local variable using that compile-time expression and it compiles and links fine. – George Houpis Jan 06 '16 at 16:57
  • @PiotrSkotnicki Of course, they don't, but at least it should end with an error in both cases or any. It is an error or it is not, it cannot (or better, it shouldn't) be an error for a compiler and not an error for another one!! :-) – skypjack Jan 06 '16 at 16:57
  • @skypjack: No, that's where you fail. There is no guarantee anything which is *potentially* ODR-used, *is* actually ODR-used. – Deduplicator Jan 06 '16 at 17:01
  • @Deduplicator Ok, got it. Can you integrate your answer with details about that, for it seems to be what I was looking for and where I was failing? Thank you very much. – skypjack Jan 06 '16 at 17:04

1 Answers1

5

The selected prototype for min is

template<class T> 
/* constexpr since C++14 */ const T& min( const T& a, const T& b );

The pertinent point is that it takes the argument by reference, meaning it One-Definition-Rule (ODR)-uses it.
And you never defined it, you only declared it in your class (with an initializer):

    static constexpr float BAR = .42;

Which is good enough for copying and otherwise using the value, but not for using it as anything but a prvalue.

See Why does constexpr static member (of type class) require a definition?

Violation of the ODR (whose finer points are fine and voluminuous indeed) need not be diagnosed:

3.2 One definition rule [basic.def.odr]

4 Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program; no diagnostic required. The definition can appear explicitly in the program, it can be found in the standard or a user-defined library, or (when appropriate) it is implicitly defined (see 12.1, 12.4 and 12.8). An inline function shall be defined in every translation unit in which it is odr-used.

Community
  • 1
  • 1
Deduplicator
  • 44,692
  • 7
  • 66
  • 118