94

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?

6
  • 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. Commented Mar 22, 2011 at 13:27
  • 1
    @Space the real function was a template, I'll edit my question to mention that.
    – JaredC
    Commented Mar 22, 2011 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
    Commented Mar 22, 2011 at 13:34
  • 2
    The annoying thing is, this error may show in innocuous uses like std::min( some_val, kConst), since std::min<T> 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
    Commented Nov 20, 2015 at 20:57
  • 1
    A quick workaround suggestion in case you don't want to change much: foo(kConst+0); Commented Nov 1, 2016 at 12:20

9 Answers 9

70

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 ;-)

5
  • 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
    Commented Mar 22, 2011 at 13:39
  • For complete conformance, I think you'd have to define something like template <int N> int intvalue() { return N; }. Then with intvalue<kConst>, 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. Commented Mar 22, 2011 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
    Commented Jan 9, 2014 at 15:59
  • 2
    ...and std::min / std::max, which led me here!
    – sage
    Commented Mar 2, 2015 at 4: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
    Commented Nov 20, 2015 at 21:13
35

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;
1
  • 1
    Thank you for this illustrative example.
    – shuhalo
    Commented Aug 13, 2017 at 5:27
15

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.

4
  • 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
    Commented Jan 2, 2016 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
    Commented Jan 4, 2016 at 9:12
  • I see it when compiling a static library.
    – quant_dev
    Commented Jan 5, 2016 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
    Commented Jan 6, 2016 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.

3
  • Taking a reference of it still requires it to be present in linker table. At least, that's what I'm seeing. Commented Aug 26, 2021 at 7:14
  • @Osman-pasha yes, but in C++17 this line inside a class define a inline variable, which manages the definition automatically. Commented Aug 26, 2021 at 12:17
  • Thanks, your answer was the most useful one.
    – iliar
    Commented Dec 28, 2022 at 16:45
3

You can also replace it by a constexpr member function:

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

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

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);
}
1
  • 1
    This is basically what I was achieving by changing it to foo(static_cast<int>(kConst));, right?
    – JaredC
    Commented Mar 22, 2011 at 13:33
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.

4
  • 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
    Commented Sep 21, 2017 at 10:19
  • What is the best way to refuse references? I'm currently doing static_cast<decltype(kConst)>(kConst).
    – Velkan
    Commented Jun 21, 2018 at 8:41
  • @Velkan I'd like to know how to do that too. Your tatic_cast<decltype(kConst)>(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
    Commented Feb 2, 2019 at 3: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
    Commented Feb 3, 2019 at 17:35
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.

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