1

The example program is here:

test.h

#pragma once
#include <bitset>

class Foo{
public:
    constexpr static std::bitset<9> hori1{0b111000000};
    bool Bar(const std::bitset<9> cells);
};

test.cpp

#include <bitset>
#include "test.h"

bool Foo::Bar(const std::bitset<9> cells){
    return hori1.any();
}

int main(){
    return 0;
}

When compiling this program without the --std=c++11 everything works fine.

$ g++ -c test.cpp
$ g++ test.o

But when the flag is included, I get this:

$ g++ -c --std=c++11 test.cpp
$ g++ test.o
/usr/bin/ld: test.o: warning: relocation against `_ZN3Foo5hori1E' in read-only section `.text'
/usr/bin/ld: test.o: in function `Foo::Bar(std::bitset<9ul>)':
test.cpp:(.text+0x17): undefined reference to `Foo::hori1'
/usr/bin/ld: warning: creating DT_TEXTREL in a PIE
collect2: error: ld returned 1 exit status

Why is this happening only in C++11? As far as I know, test.h is correctly included and therefore hori1 should be visible from within Foo. Any help is appreciated.

273K
  • 29,503
  • 10
  • 41
  • 64
Jan
  • 25
  • 6
  • Do you get any issue if you do the compilation and linking in one call to g++? Wondering if there's some linking flag. Also, `0b` integer literal notation isn't legal until C++14. I guess gcc implemented it as an extension in 11? – JohnFilleau Mar 13 '22 at 14:47
  • @JohnFilleau It has the same behavior as when compiling and linking separately. Works without C++11 flag, doesn't work with it. – Jan Mar 13 '22 at 14:54
  • @Jan refer this https://stackoverflow.com/questions/18749071/why-does-a-static-data-member-need-to-be-defined-outside-of-the-class – long.kl Mar 13 '22 at 14:56
  • 1
    @long.kl This does not apply to constexpr static members. In fact, `constexpr static` members **must** be defined inside the class. – Jan Mar 13 '22 at 15:01

2 Answers2

3

hori1 is static. You have to define static data members in the implementation of the class.

test.cpp:

constexpr std::bitset<9> Foo::hori1;

bool Foo::Bar(const std::bitset<9> cells){
    return hori1.any();
}

int main(){
    return 0;
}
afekete
  • 71
  • 6
  • What is the header supposed to look like then? Static const data members require in-class initialization, so I can't leave only the declaration in the header file. – Jan Mar 13 '22 at 14:44
  • The declaration is correct in the header file, you can leave it as it was along with initialization. – afekete Mar 13 '22 at 14:47
  • It works, but it looks bizzare. The header file has the definition, but the declaration is in the cpp file. Is that really the correct way to do it? – Jan Mar 13 '22 at 14:49
  • @Jan you can keep the header as declaration and source file as definition of you prefer the way it looks. – lurker Mar 13 '22 at 14:56
  • 1
    @afekete I think that rule only applies to complex types. Basic types like `int` can be defined as static solely in the header. – lurker Mar 13 '22 at 14:57
  • @lurker That's the problem, I can't. `constexpr static` class members must have an in-class definition. You can't leave just the declaration in the header. – Jan Mar 13 '22 at 14:58
  • @Jan I'm not sure what you tried but I do this regularly. Let me be cleared though... Put the declaration in both places but the initializer in the source. – lurker Mar 13 '22 at 14:59
  • @lurker Yes, that works with `static` members, but NOT with `constexpr static`. If I put the line `constexpr static std::bitset<9> hori1;` *inside* my class, then I get the error: `error: ‘constexpr’ static data member ‘hori1’ must have an initializer` – Jan Mar 13 '22 at 15:04
  • @Jan ah I see. Yeah I had not tried it with constexpr. The constness requires the initializer in the header definition, but complex static variables must also be declared in the source. – lurker Mar 13 '22 at 15:17
  • According to the standard about `constexpr static` member variables: "A static data member of literal type can be declared in the class definition with the constexpr specifier; if so, its declaration shall specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression." This means you have to initialize complex types at declaration. @lurker thank you for specifying the rule. A similar post: https://stackoverflow.com/questions/34053606/understanding-static-constexpr-member-variables – afekete Mar 13 '22 at 15:18
  • @afekete I'm aware of that, and I implemented it correctly in the code in my question. Still, why doesn't the code link correctly then? – Jan Mar 13 '22 at 15:21
  • @Jan `std::bitset` isn't a "literal" type. – lurker Mar 13 '22 at 18:48
1

The problem is that in C++11, we have to add a corresponding definition outside the class in exactly one translation unit for a static constexpr declaration of a class' data member(if odr-used). This is explained in more detail below:

C++11

class Foo
{
public:
    static constexpr int OUT_OF_BOUNDS_VALUE = -9999; //THIS IS A DECLARATION IN C++11 and C++14
    //other members here
};

In the above code snippet(which is for C++11,C++14), we have a declaration of the static data member OUT_OF_BOUNDS_VALUE inside the class. And so, in exactly one translation unit we have to provide a corresponding definition. Otherwise you'll get a linker error which can be seen here.

That is, in exactly one translation unit we should write:

constexpr int Foo::OUT_OF_BOUNDS_VALUE;//note no initializer

After adding the above out of class definition with no initializer the program will work. Demo

C++17

class Foo
{
public:
    static constexpr int OUT_OF_BOUNDS_VALUE = -9999; //THIS IS A DEFINITION IN C++17
    //other members here
};

In the above code snippet(which is for C++17) we have a definition of the static data member OUT_OF_BOUNDS_VALUE inside the class. So since C++17, we don't have to provide the definition of OUT_OF_BOUNDS_VALUE anywhere else since we already have a definition for it inside the class and thus the same program works without any linker error.

Jason
  • 36,170
  • 5
  • 26
  • 60
  • Could you explain why is the line in the cpp file called a "definition"? Also, why is it `constexpr const` and not `static constexpr`? Also what if it were just `constexpr` without the `const`? Thank you. – Jan Mar 13 '22 at 17:02
  • @Jan There is no `const` there. That was a typo that i forgot to remove. I have updated the examples in my answer. Check out my updated version. – Jason Mar 13 '22 at 17:11
  • @Jan Now coming to your other question about why the line in cpp file is a definition: From [Constant static members](https://en.cppreference.com/w/cpp/language/static#Constant_static_members): *"If a `const` non-inline (since C++17) static data member or a `constexpr` static data member (since C++11)(until C++17) is odr-used, a **definition** at namespace scope is still required, but it **cannot have an initializer**. A definition may be provided even though **redundant (since C++17)**. "* – Jason Mar 13 '22 at 17:16
  • @Jan Did you take a look at my edited answer? There is no `const` there. – Jason Apr 05 '22 at 09:48