Strange C++ link error
Asked Answered
B

3

9

When I try to compile this,

#include <iostream>

struct K{
    const static int a = 5;
};

int main(){
    K k;

    std::cout << std::min(k.a, 7);
}

I get following. Both gcc and clang gives similar error:

/tmp/x-54e820.o: In function `main':
x.cc:(.text+0xa): undefined reference to `K::a'
clang-3.7: error: linker command failed with exit code 1 (use -v to see invocation)

if I do following, it compiles without problem. Is this related to the way std::min is written?

#include <iostream>

struct K{
    const static int a = 5;
};

int main(){
    K k;

    std::cout << std::min((int) k.a, 7);  // <= here is the change!!!
}

another way to avoid the error is if I do my own min():

template <class T>
T min(T const a, T const b){
    return a < b ? a : b;
}

C-like preprocessor MIN also works OK.

Bostwick answered 22/3, 2016 at 12:35 Comment(6)
Why are gcc and clang tagged? you should only be using one compiler which by the looks of it is clangPennon
ideone.com/yPtq6w works for meUri
Try changing your min to take the parameters by reference.Teage
Your examples should have #include <algorithm> for std::minLavinialavinie
Since C++11 another workaround is to use the list-form of min: std::min({k.a, 7}) . I think this is not odr-use because initializer lists copy by value. Since C++14 the list form even yields a constexpr.Lavinialavinie
Also see the answer to #9928873Halogenate
L
8

std::min accepts arguments by reference. Binding a reference to an object means that the object is odr-used (there is a code sample in [basic.def.odr]/2 pretty much the same as your sample).

However in the (int)k.a case, k.a is not odr-used; because it is performing lvalue-to-rvalue conversion which yields a constant expression. (There are a few other conditions here too but your code is OK).

If an object is odr-used then there must be exactly one definition of it; with no diagnostic required for violating this rule. So the first case may or may not be accepted; and the second case must be accepted.

In your own version of min, it takes arguments by value, which is similar to the (int)k.a case - the only action taken on k.a there is rvalue conversion to initialize the parameter of your min.

You can read the full set of rules about odr-use in section [basic.def.odr] of a C++ standard draft.

Lavinialavinie answered 22/3, 2016 at 12:46 Comment(3)
a line const int K::a; is whats missingGladdie
@Gladdie not necessarily; e.g. the second sample is correct without that lineLavinialavinie
BTW, 9.4.2 Static data members [class.static.data] §3 is specifically explicit for that use case If a non-volatile const static data member is of integral or enumeration type, its declaration in the class definition can specify a brace-or-equal-initializer in which every initializer-clause that is an assignmentexpression is a constant expression ... The member shall still be defined in a namespace scope if it is odr-used (3.2) in the program and the namespace scope definition shall not contain an initializer.Peabody
S
2

This question is asked quite often. I believe it's a bug in clang. a is being detected as a constant expression too early and the compiler is not generating a definition of it. (see correction in comments)

std::min takes its arguments by const reference, so a definition must exist.

#include <iostream>

struct K{
    const static int a = 5;
};

int main(){
    K k;

    std::cout << std::min(k.a, 7);
}

Here's a portable workaround alternative:

#include <iostream>

struct K{
    constexpr static int a() { return 5; }
};


int main(){
    K k;

    std::cout << std::min(k.a(), 7);
}
Scold answered 22/3, 2016 at 12:39 Comment(5)
IIRC also the optimization level might affect this.Heartwood
@Heartwood it fails to link on apple clang with no optimisation. The compiler is failing to figure out the ODR-use of a correctly.Scold
@Richard Hodges Ok, I just remember I have run into this with GCC on Ubuntu and it worked without optimizations, but failed with -O3 or something like that :pHeartwood
No it is not a bug (I thought it was too...). But a static const integral initialisation is still not a definition according to standard : 9.4.2 Static data members [class.static.data] §3 If a non-volatile const static data member is of integral or enumeration type, its declaration in the class definition can specify a brace-or-equal-initializer in which every initializer-clause that is an assignmentexpression is a constant expression ... The member shall still be defined in a namespace scope if it is odr-used (3.2) ... and the namespace scope definition shall not contain an initializer.Peabody
@SergeBallesta correction made to post in response. Thank you.Scold
C
-2

Your have declared a static variable (a) in your struct, but you have not defined it.

struct K
{
    const static int a; // declaration
};

const int K::a = 5; // definition 


int main()
{
    std::cout << std::min(K::a, 7);
}

You may find this link helpful.

I also agree with Richard Hodges answer.

Cinemascope answered 22/3, 2016 at 13:50 Comment(2)
This would not work for the same reason that OP's attempt would not work.Fernandina
You are right. I was using g++ -std=c++11 -O2 -Wall and it worked. Removing -O2 make it doesn't. I'm going to edit the answer.Cinemascope

© 2022 - 2024 — McMap. All rights reserved.