87

I ran into an interesting issue today. Consider this simple example:

template <typename T>
void foo(const T & a) { /* code */ }

// This would also fail
// void foo(const int & a) { /* code */ }

class Bar
{
public:
   static const int kConst = 1;
   void func()
   {
      foo(kConst);           // This is the important line
   }
};

int main()
{
   Bar b;
   b.func();
}

When compiling I get an error:

Undefined reference to 'Bar::kConst'

Now, I'm pretty sure that this is because the static const int is not defined anywhere, which is intentional because according to my understanding the compiler should be able to make the replacement at compile-time and not need a definition. However, since the function takes a const int & parameter, it seems to be not making the substitution, and instead preferring a reference. I can resolve this issue by making the following change:

foo(static_cast<int>(kConst));

I believe this is now forcing the compiler to make a temporary int, and then pass a reference to that, which it can successfully do at compile time.

I was wondering if this was intentional, or am I expecting too much from gcc to be able to handle this case? Or is this something I shouldn't be doing for some reason?

JaredC
  • 5,150
  • 1
  • 20
  • 45
  • 1
    In practice you could just do `const int kConst = 1;` with the same result. Also, there is rarely a reason (I can think of none) to have a function take a parameter of the type `const int &` - just use an `int` here. – Björn Pollex Mar 22 '11 at 13:27
  • 1
    @Space the real function was a template, I'll edit my question to mention that. – JaredC Mar 22 '11 at 13:29
  • 1
    @Space fyi, not making it `static` gives an error `ISO C++ forbids the initialization of member 'kConst'...making 'kConst' static.' – JaredC Mar 22 '11 at 13:34
  • 1
    The annoying thing is, this error may show in innocuous uses like `std::min( some_val, kConst)`, since `std::min` has parameters of type `T const &`, and the implication is that we need to pass a reference to *the* kConst. I found it occurred only when optimization was off. Fixed using a static cast. – greggo Nov 20 '15 at 20:57
  • A quick workaround suggestion in case you don't want to change much: `foo(kConst+0);` – BIOStheZerg Nov 01 '16 at 12:20
  • A much better workaround: `foo(+kConst);` :) – Ethouris Sep 19 '17 at 09:28

9 Answers9

66

It's intentional, 9.4.2/4 says:

If a static data member is of const integral or const enumeration type, its declaration in the class definition can specify a constant-initializer which shall be an integral constant expression (5.19) In that case, the member can appear in integral constant expressions. The member shall still be defined in a namespace scope if it is used in the program

When you pass the static data member by const reference, you "use" it, 3.2/2:

An expression is potentially evaluated unless it appears where an integral constant expression is required (see 5.19), is the operand of the sizeof operator (5.3.3), or is the operand of the typeid operator and the expression does not designate an lvalue of polymorphic class type (5.2.8). An object or non-overloaded function is used if its name appears in a potentially-evaluated expression.

So in fact, you "use" it when you pass it by value too, or in a static_cast. It's just that GCC has let you off the hook in one case but not the other.

[Edit: gcc is applying the rules from C++0x drafts: "A variable or non-overloaded function whose name appears as a potentially-evaluated expression is odr-used unless it is an object that satisfies the requirements for appearing in a constant expression (5.19) and the lvalue-to-rvalue conversion (4.1) is immediately applied.". The static cast performs lvalue-rvalue conversion immediately, so in C++0x it's not "used".]

The practical problem with the const reference is that foo is within its rights to take the address of its argument, and compare it for example with the address of the argument from another call, stored in a global. Since a static data member is a unique object, this means if you call foo(kConst) from two different TUs, then the address of the object passed must be the same in each case. AFAIK GCC can't arrange that unless the object is defined in one (and only one) TU.

OK, so in this case foo is a template, hence the definition is visible in all TUs, so perhaps the compiler could in theory rule out the risk that it does anything with the address. But in general you certainly shouldn't be taking addresses of or references to non-existent objects ;-)

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • 1
    Thanks for the example of taking the address of the reference. I thinks that's a real practical reason why the compiler doesn't do what I expect. – JaredC Mar 22 '11 at 13:39
  • For complete conformance, I think you'd have to define something like `template int intvalue() { return N; }`. Then with `intvalue`, `kConst` only appears in a context requiring an integral constant expression, and so is not used. But the function returns a temporary with the same value as `kConst`, and that can bind to a const reference. I'm not sure, though, there might be a simpler way to portably enforce that `kConst` is not used. – Steve Jessop Mar 22 '11 at 13:47
  • 1
    I experience the same issue by using such static const variable in a ternary operator (ie something like `r = s ? kConst1 : kConst2`) with gcc 4.7. I solved it by using an actual `if`. Anyway thanks for the answer! – Clodéric Jan 09 '14 at 15:59
  • 2
    ...and std::min / std::max, which led me here! – sage Mar 02 '15 at 04:39
  • "AFAIK GCC can't arrange that unless the object is defined in one (and only one) TU". It's too bad, since it's already possible to do that with constants - compile them multiple times as 'weak defs' in .rodata, and then have the linker just pick one - which ensures that all actual refs to it would have the same address. This is actually what is done for typeid; it can fail in weird ways, though, when shared libs are used. – greggo Nov 20 '15 at 21:13
31

If you're writing static const variable with initializer inside class declaration it's just like as if you've written

class Bar
{
      enum { kConst = 1 };
}

and GCC will treat it the same way, meaning that it does not have an address.

The correct code should be

class Bar
{
      static const int kConst;
}
const int Bar::kConst = 1;
pelya
  • 4,326
  • 2
  • 24
  • 23
14

This is a really valid case. Especially because foo could be a function from the STL like std::count which takes a const T& as its third argument.

I spent much time trying to understand why the linker had problems with such a basic code.

The error message

Undefined reference to 'Bar::kConst'

tells us that the linker cannot find a symbol.

$nm -C main.o
0000000000000000 T main
0000000000000000 W void foo<int>(int const&)
0000000000000000 W Bar::func()
0000000000000000 U Bar::kConst

We can see from the 'U' that Bar::kConst is undefined. Hence, when the linker tries to do its job, it has to find the symbol. But you only declare kConst and don't define it.

The solution in C++ is also to define it as follows:

template <typename T>
void foo(const T & a) { /* code */ }

class Bar
{
public:
   static const int kConst = 1;
   void func()
   {
      foo(kConst);           // This is the important line
   }
};

const int Bar::kConst;       // Definition <--FIX

int main()
{
   Bar b;
   b.func();
}

Then, you can see that the compiler will put the definition in the generated object file:

$nm -C main.o
0000000000000000 T main
0000000000000000 W void foo<int>(int const&)
0000000000000000 W Bar::func()
0000000000000000 R Bar::kConst

Now, you can see the 'R' saying that it is defined in the data section.

Stac
  • 164
  • 1
  • 6
  • Is it OK that a constant appears in "nm -C" output twice, first with an "R" and an address, and then with an "U"? – quant_dev Jan 02 '16 at 23:08
  • Do you have any example ? On the provided example, `>nm -C main.o | grep kConst` gives me only one line `0000000000400644 R Bar::kConst`. – Stac Jan 04 '16 at 09:12
  • I see it when compiling a static library. – quant_dev Jan 05 '16 at 12:31
  • 1
    In this case, of course ! A static library is only an aggregate of object files. The linkage is only done by the client of the static lib. So if you put in the archive file, the object file containing the definition of the const and another object file calling Bar::func(), you will see the symbol once with the definition and once without: `nm -C lib.a` gives you `Constants.o: 0000000000000000 R Bar::kConst` and `main_file.o: U Bar::kConst ...`. – Stac Jan 06 '16 at 14:23
7

The proper way in C++17 would be to simply make the variable constexpr:

static constexpr int kConst = 1;

constexpr static data member are implicitly inline.

Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
3

You can also replace it by a constexpr member function:

class Bar
{
  static constexpr int kConst() { return 1; };
};
Yoav
  • 5,962
  • 5
  • 39
  • 61
  • Note that this requires both 4 lines in the declaration and braces after the "constant", so you end up writing foo = std::numeric_limits::max() * bar::this_is_a_constant_that_looks_like_a_method() and hoping your coding standard copes and the optimiser fixes it for you. – Code Abominator Nov 11 '18 at 22:26
2

g++ version 4.3.4 accepts this code (see this link). But g++ version 4.4.0 rejects it.

TonyK
  • 16,761
  • 4
  • 37
  • 72
1

Simple trick: use + before the kConst passed down the function. This will prevent the constant from being taken a reference from, and this way the code will not generate a linker request to the constant object, but it will go on with the compiler-time constant value instead.

Ethouris
  • 1,791
  • 13
  • 18
  • 1
    It's a pity though that the compiler doesn't issue a warning when an address is taken from a `static const` value that is initialized at the declaration. This would always lead to linker error, and when the same constant is also declared separately in an object file, this would be an error, too. The compiler is also fully aware of the situation. – Ethouris Sep 21 '17 at 10:19
  • What is the best way to refuse references? I'm currently doing `static_cast(kConst)`. – Velkan Jun 21 '18 at 08:41
  • @Velkan I'd like to know how to do that too. Your tatic_cast(kConst) trick doesn't work in the case that kConst is a char[64]; it gets " error: static_cast from 'char *' to 'decltype(start_time)' (aka 'char [64]') is not allowed". – Don Hatch Feb 02 '19 at 03:24
  • @DonHatch, I'm not into software archaeology, but as far as I remember it's very hard to pass a raw array into function by copying. So syntactically the `foo()` from the original question will be needing the address, and there is no mechanism to treat it as a temporary copy of the whole array. – Velkan Feb 03 '19 at 17:35
1

I think this artefact of C++ means that any time that Bar::kConst is referred to, its literal value is used instead.

This means that in practise there is no variable to make a reference point to.

You may have to do this:

void func()
{
  int k = kConst;
  foo(k);
}
quamrana
  • 37,849
  • 12
  • 53
  • 71
  • 1
    This is basically what I was achieving by changing it to `foo(static_cast(kConst));`, right? – JaredC Mar 22 '11 at 13:33
0

I experienced the same problem as mentioned by Cloderic (static const in a ternary operator: r = s ? kConst1 : kConst2), but it only complained after turning off compiler optimization (-O0 instead of -Os). Happened on gcc-none-eabi 4.8.5.

Scg
  • 31
  • 1