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

Leedehai
  • 3,660
  • 3
  • 21
  • 44
  • @tkausl Thanks for the comment, but no, I don't think so. I read that. – Leedehai Apr 24 '18 at 18:32
  • Works for me though: http://coliru.stacked-crooked.com/a/d47b592177704b22 – tkausl Apr 24 '18 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 Apr 24 '18 at 18:44
  • @Leedehai You sure you did `const int A::STAT_MEMBER;` in the `cc` properly? – legends2k Apr 24 '18 at 18:45
  • @legends2k no, that's why this is a question. – Mark Ransom Apr 24 '18 at 18:47
  • I didn't mean to close but that other question deals with exactly the same case `v.push_back( Foo::MEMBER );`... – user7860670 Apr 24 '18 at 18:51
  • @VTT Thx, but I read that, too ([link](https://stackoverflow.com/questions/272900/undefined-reference-to-static-class-member)). IMHO, that question didn't ask as deep as this one. – Leedehai Apr 24 '18 at 18:55
  • Oh... reopening it dropped all the duplicated question links... – user7860670 Apr 24 '18 at 18:58
  • @Leedehai This is really no duplicate, because it quests the *why* and not the "how to solve part". My answer give you the reason... – Klaus Apr 24 '18 at 19:00

3 Answers3

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!

Klaus
  • 24,205
  • 7
  • 58
  • 113
  • 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? – Mark Ransom Apr 24 '18 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 Apr 24 '18 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 Apr 24 '18 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 Apr 24 '18 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 Apr 24 '18 at 19:02
  • @Klaus ok, that makes sense.. so you mean it's just how the language was designed (as of C++14). – Leedehai Apr 24 '18 at 19:05
  • @Leedehai: Yes, it is simply how it works: Use pointer or reference means accessing the memory where the object is located. If not defined, you can't access, results in linker error. Value is known, no memory needed for access, code works. :-) This is still the same in c++17! The only change is that static constexpr is defined by default and "redefinition" is deprecated. See https://stackoverflow.com/questions/44721729/constexpr-definition-and-declaration-for-constexpr-members – Klaus Apr 24 '18 at 19:16
  • @Leedehai I don't think this is the complete answer. The compiler is able to initialize a temporary and pass either a const reference or an rvalue reference to `push_back` as can be seen in this example: https://ideone.com/ElNeiT. The example doesn't have an object with permanent storage. – Mark Ransom Apr 24 '18 at 20:14
  • @MarkRansom I think you may find your question addressed in the comments under this answer: ([link](https://stackoverflow.com/a/50009099/8385554)) – Leedehai Apr 25 '18 at 00:12
  • @Leedehai so because the declaration is compatible with an lvalue it tries to use it as such, then fails in the linker? I guess that's plausible. Not at all intuitive though, you'd expect the `= 1` in the declaration to make it incompatible with an lvalue. – Mark Ransom Apr 25 '18 at 19:13
  • @MarkRansom That's my understanding. I tried to get the address of a class's static data member (C++14), and the linker gave me an "undefined reference" error. – Leedehai Apr 25 '18 at 19:37
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;
};
YePhIcK
  • 5,816
  • 2
  • 27
  • 52
  • `constexpr` is more appropriate than `inline` here? It [works](http://coliru.stacked-crooked.com/a/6de36bd55005feb7) with that too. – legends2k Apr 24 '18 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; 
user7860670
  • 35,849
  • 4
  • 58
  • 84
  • 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 Apr 24 '18 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. – user7860670 Apr 24 '18 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 Apr 24 '18 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};`. – user7860670 Apr 24 '18 at 19:30