Search code examples
c++c++11staticconstexprstl-algorithm

Static constexpr members seem not to go along with std::min


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.


Solution

  • 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.