4

So yesterday I was looking on SO and could not find an answer to the following. This situation came from some come code I am working with, but here is the MCVE to demonstrate it.

I have a class A defined in A.h that just has one static const in it. I already initialized it in the header.

#ifndef A_H_
#define A_H_
class A {
public:
    static const int test = 5;
    ~A(){};
};


#endif /* A_H_ */

I then have a class B that needs to access the public static const from class A. In this example it will deep copy the value to a vector.

#ifndef B_H_
#define B_H_

#include "A.h"
#include <vector>
#include <iostream>

class B {
private:
    std::vector<int> testVec;

public:
    B(){

        testVec.push_back((const int)A::test);
        //testVec.push_back(static_cast<const int>(A::test)); //Also works
        //testVec.push_back(A::test); //Doesn't work without forward declaration of const int A::test in main or in this header 
        //(Compiler link error: undefined reference to `A::test')  
        std::cout<< testVec.front() << std::endl;
    }

    ~B(){};
};

#endif /* B_H_ */

Then in main I simple call the constructor of class B.

#include "B.h"

int main() {

    B b;

    return 0;
}
//Does the cout from ctor of B and prints 5 to the screen. 

My question is why does a normal cast or static cast allow me to access this static const variable that has not been forward declared. In normal code, I would forward declare the variable or declare it an extern since it has already been defined. What is the reason as to why the casting allows me to access this variable without a forward declaration? (This may seem like a simple question and may have a simple answer, but I am trying to further my knowledge here).

The output from the compiler link error is:

Invoking: Cygwin C++ Linker
g++  -o "S_Test_p1.exe"  ./src/S_Test_p1.o   
./src/S_Test_p1.o:S_Test_p1.cpp:(.rdata$.refptr._ZN1A4testE[.refptr._ZN1A4testE]+0x0): undefined reference to `A::test'
collect2: error: ld returned 1 exit status
make: *** [makefile:47: S_Test_p1.exe] Error 1

My main question here is why the casting works and not that the solution is to define A::test in main or in B.h (which I know works). I understand that would be accepted and proper. The main question is about the unaccepted way, which is casting. Behind the scenes why does casting work for linking?

9Breaker
  • 724
  • 6
  • 16
  • 2
    Why exactly does `testVec.push_back(A::test);`not work? It should ... – Rene Oct 18 '17 at 12:29
  • @Rene because static should be defined somewhere and stored somewhere – Sergey.quixoticaxis.Ivanov Oct 18 '17 at 12:32
  • @Rene It will not link and I have tried this with the MULTI compiler for my embedded system and with Cygwin GCC. Output: undefined reference to `A::test' – 9Breaker Oct 18 '17 at 12:33
  • Don't tell me. I want the OP to post the error he gets, so we can solve this error. The casts however are not the right solution. – Rene Oct 18 '17 at 12:33
  • @Rene I know the solution is forward declaration, and that it will link with that. But why does the cast work? That is my question. – 9Breaker Oct 18 '17 at 12:35
  • @9Breaker As I expected. Sometimes `static const int ...` member variables are not directly replaced by the compiler, means you have to add a definition line in your A.cpp: `const int A::test;` – Rene Oct 18 '17 at 12:35
  • @Rene Right, I know that works. But how does casting essentially achieve the same thing? – 9Breaker Oct 18 '17 at 12:45

3 Answers3

7

A declaration of a static member within a class like static const int test = 5; is a declaration but not a definition, even if it has an initializer. A declaration should normally have a corresponding definition. This definition looks like const int A::test; (which is not a "forward declaration".)

However, there's an additional rule that a static const class member of integer type does not need to be defined if only its value is used, its address is not taken, and no reference binds to it (which would be similar to taking its address).

The function you're calling is void std::vector<int>::push_back(const int&);. So passing A::test directly will bind the function parameter reference directly to the object, and require a definition.

On the other hand, if you pass (const int)A::test or static_cast<const int>(A::test), this forces a temporary int value to be created using the value of A::test, and the reference binds to that temporary instead. So in that case, a definition of A::test is not necessary.

Note in C++17, the definition of A::test won't be necessary in any case, since a static class member with an initializer in the class definition is implicitly an inline variable and a definition.

Until then, be sure to define all your class static members in some *.cpp file, in case you do use them in a way that requires a definition.

aschepler
  • 70,891
  • 9
  • 107
  • 161
1

Your code is equivalent to this simplified1 snippet:

struct A { static const int value = 5; };
struct B { const int& n ; B() : n(A::value) {} };

int main()
{
    B b;
}

Which is ill-formed since A::value is not defined (only declared). My compiler report a linker error:

main.cpp:(.text._ZN1BC2Ev[_ZN1BC5Ev]+0xf): undefined reference to `A::value'

A solution would be to define it properly:

struct A { static const int value = 5; };
struct B { const int& n ; B() : n(A::value) {} };
const int A::value;

int main()
{
    B b;
}

1) The important part of your example is the usage of a reference to A::test (see definition of std::vector::push_back(). This is why I defined B to use a reference to A::value.

YSC
  • 38,212
  • 9
  • 96
  • 149
  • That is all right and I agree with what you have said. My main question is more on why casting works (which I know would not be accepted coding standard, but I am wondering why the cast works) – 9Breaker Oct 18 '17 at 12:44
  • 1
    @9Breaker casting creates temporary. I was about to add it but aschepler beat me to it ;) – YSC Oct 18 '17 at 12:54
0

The cast is not needed, and it has nothing to do with the question.

The #include "B.h" at the top of main says to include the contents of the file "B.h". When that's done, the #include "A.h" in "B.h" says to include the contents of the file "A.h". "A.h" has the definition of A, and that's how the compiler knows about A::test. There's no need for a forward declaration (in fact, you can't have a forward declaration of a class member) because the compiler has seen the full definition of the class.

The linking problem is because you must also define the static member somewhere. Typically you'll have a .cpp file that has the definitions of the member functions of the class. That file should also define the static member:

const int A::test;
Pete Becker
  • 74,985
  • 8
  • 76
  • 165
  • Yes, but it does not link without forward declaration even when the header A.h is included. Casting will work and it links fine. Or forward declaration of const int A::test in main or in B.h will also link well. My question is how does the cast make the linking work without forward declaration? – 9Breaker Oct 18 '17 at 12:38