2

EDIT: I'm not asking how to make the code work, but why it has error. Also, the static member is not only declared inside the class, but also has been initialized with a value (at least it appears to have), hence I don't think it's redundant. Also, this question asks beyond that, so please don't close it. It is not a duplicate.

Say class A has a static member data defined inside. The shell record below does not link (ld: undefined reference).

Note that since C++11, std::vector<>::push_back() accepts either a const lvalue reference (const value_type&) or an rvalue reference (value_type&& val). Moreover, even if it doesn't accept an rvalue reference, an rvalue can be bound to a const lvalue reference.

$ cat class.h
struct A {
    static const int STAT_MEMBER = 1;
    A();
    int a;
};

$ cat class.cc
#include "class.h"
#include <vector>
using namespace std;
A::A() { a = STAT_MEMBER; }

int main() {
    vector<int> v;
    v.push_back(A::STAT_MEMBER);
}

$ g++ -std=c++14 class.cc -o class
/tmp/ccNXDnyu.o: In function `main':
class.cc:(.text+0x2f): undefined reference to `A::STAT_MEMBER'
collect2: error: ld returned 1 exit status

$

Question:

(1) is A::STATIC_MEMBER an rvalue? (but regardless of it being an rvalue or an lvalue, it shouldn't cause an error in linker -- it should have been fine or have caused an error in compiler)

(2) why didn't the compiler/linker complain about STATIC_MEMBER in A::A()?

(3) why didn't the compiler complain about STATIC_MEMBER in main()?

(4) why did the linker complain about STATIC_MEMBER in main()?

I know this problem should be gone if the static member is declared inside the class but defined outside the class, but I'm mostly interested the problem above.

G++ version: g++-6 (Ubuntu/Linaro 6.3.0-18ubuntu2~14.04) 6.3.0 20170519

9
  • @tkausl Thanks for the comment, but no, I don't think so. I read that.
    – Leedehai
    Commented Apr 24, 2018 at 18:32
  • Works for me though: coliru.stacked-crooked.com/a/d47b592177704b22
    – tkausl
    Commented Apr 24, 2018 at 18:40
  • @tkausl Weird! It works for me, too, ONLY IF the entire code reside in one source file. If I move the definition of class A to the header, it doesn't work (tried again).
    – Leedehai
    Commented Apr 24, 2018 at 18:44
  • @Leedehai You sure you did const int A::STAT_MEMBER; in the cc properly?
    – legends2k
    Commented Apr 24, 2018 at 18:45
  • @legends2k no, that's why this is a question. Commented Apr 24, 2018 at 18:47

3 Answers 3

2

The reason is quite easy:

If you use a value, the compiler can use the known value to your undefined but declared static const member.

But the vector::push_back takes the reference to the value! The reference is the address and there is no address of an undefined object, only the value of the object is known.

You can reduce the code to this, which simplifies it a bit:

struct A {
    static const int STAT_MEMBER = 1;
};

void Works( const int i ){} // this takes the VALUE of the variable

void Fail( const int& i){} // this takes the ADDRESS of the variable
                           // but there is no object which can be 
                           // addressed, because it is not defined!

int main()
{
    Works( A::STAT_MEMBER );
    Fail( A::STAT_MEMBER );
}

Simply comment in and out to get linker error or not!

11
  • As stated in the question, the compiler should have been able to create a temporary object, initialized to the value, and given a const reference to it. What rule prevents it? Commented Apr 24, 2018 at 18:49
  • Interesting, could you elaborate on what kind of value the "undefined but declared static const member" is? Is initializing it a value (like I did in code) not enough? I thought it's an rvalue. (yes, my question is the as @MarkRansom)
    – Leedehai
    Commented Apr 24, 2018 at 18:50
  • BTW, I ran your suggested simplification - yes, linker error. "/tmp/ccQSSYSY.o: In function main': class3.cc:(.text+0x24): undefined reference to A::STAT_MEMBER' collect2: error: ld returned 1 exit status"
    – Leedehai
    Commented Apr 24, 2018 at 18:50
  • @MarkRansom: No, if you request the address of an variable, you can not generate a temporary only to get an address of an temporary. The compiler can not know what will be done later with the address. Maybe someone wants to get a pointer from it which is a bad idea for a temporary. But no need to discuss it, it simply is the language.
    – Klaus
    Commented Apr 24, 2018 at 18:52
  • @Leedehai: You have a value, here it is "1". But you can not store it, because you did not define the object, it is only declared! So your code generates access to the address of the variable, but the linker can't find the variable, because it was not defined. Ok?
    – Klaus
    Commented Apr 24, 2018 at 19:02
1

This only works in C++17, so just wait a little bit (or turn on the C++17 support flag in your compiler).

From the cppreference.com:

A static data member may be declared inline. An inline static data member can be defined in the class definition and may specify an initializer. It does not need an out-of-class definition:

struct X
{
    inline static int n = 1;
};
1
  • constexpr is more appropriate than inline here? It works with that too.
    – legends2k
    Commented Apr 24, 2018 at 18:44
1

This can be simplified to this:

struct A { static const int s = 1; };

int main()
{
    int a1 = A::s; // OK
    int const & a2 = A::s; // error, 
    int const * a3 = &A::s; // same thing
}

a1 case works because of the special treatment that integral constants receive. Basically compiler knows the value of s at compile time and does not need to access storage. This also allows s to be used as template parameters:

::std::array<int,A::s> xx; 
4
  • Thanks! So you mean the reason why compiler gets the value is that it treats it like a #define? It knows the value, but didn't generate a symbol for it to link.
    – Leedehai
    Commented Apr 24, 2018 at 19:10
  • @Leedehai More like an enum values. They are static constants but without storage. It is possible to assign them to variables but not reference them. Commented Apr 24, 2018 at 19:12
  • uh wait a minute.. an rvalue, say a integer literal, also doesn't have a storage. But vector's push_back() should have accepted an rvalue.
    – Leedehai
    Commented Apr 24, 2018 at 19:22
  • 1
    @Leedehai Passing an integer literal will lead to the creation of temporary int initialized with a value of that literal and invocation of overload taking an rvalue reference. While passing A::s will invoke an overload taking an lvalue reference. Explicitly wrapping it into temporary variable will work fine int const & a4 = int{A::s};. Commented Apr 24, 2018 at 19:30

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