Error constexpr needed for in class initialization of static data member

In PHP and C# the constants can be initialized as they are declared: class Calendar3 { const int value1 = 12; const double value2 = 0.001; } I have the following C++ declaration of a func...

I ran into real problems with this, because I need the same code to compile with differing versions of g++ (the GNU C++ compiler). So I had to use a macro to see which version of the compiler was being used, and then act accordingly, like so

#if __GNUC__ > 5
 #define GNU_CONST_STATIC_FLOAT_DECLARATION constexpr
#else
 #define GNU_CONST_STATIC_FLOAT_DECLARATION const
#endif

GNU_CONST_STATIC_FLOAT_DECLARATION static double yugeNum=5.0;

This will use ‘const’ for everything before g++ version 6.0.0 and then use ‘constexpr’ for g++ version 6.0.0 and above. That’s a guess at the version where the change takes place, because frankly I didn’t notice this until g++ version 6.2.1. To do it right you may have to look at the minor version and patch number of g++, so see

https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html

for the details on the available macros.

With gnu, you could also stick with using ‘const’ everywhere and then compile with the -fpermissive flag, but that gives warnings and I like my stuff to compile cleanly.

Not great, because it’s specific to gnu compilers, butI suspect you could do similar with other compilers.

It’s interesting to see how small snippets of code can cause so much headache.

Consider this minimal example.

struct c{
    static const int i = 42;
};
int foo(const int& i) { return i; }

int main(){
    return foo(c::i);
}

It won’t compile with gcc or clang, unless compiling with optimization:

> g++ --std=c++11 main.cpp
/usr/bin/ld: /tmp/ccYtUx0X.o: warning: relocation against '_ZN1c1iE' in read-only section '.text'
/usr/bin/ld: /tmp/ccYtUx0X.o: in function 'main':
main.cpp:(.text+0x12): undefined reference to 'c::i'
/usr/bin/ld: warning: creating DT_TEXTREL in a PIE
collect2: error: ld returned 1 exit status
> g++ --std=c++11 -O1 main.cpp
# compiles without warnings

The error is cryptic for multiple reasons.

If foo takes its argument by value and not const-ref, then the code also compiles

struct c{
    static const int i = 42;
};
int foo(int i) { return i; }

int main(){
    return foo(c::i);
}

If we use a float instead of an int, we will get the following compiler error with GCC: error: 'constexpr' needed for in-class initialization of static data member 'const float c::i' of non-integral type [-fpermissive]. If we also add constexpr, the code compiles

struct c{
    static constexpr const float i = 42;
};
int foo(const int& i) { return i; }

int main(){
    return foo(c::i);
}

But if I change the signature of int foo(const int&) to int foo(const float&), then the issue is there again

struct c{
    static const constexpr float i = 42;
};
int foo(const float& i) { return i; }

int main(){
    return foo(c::i);
}

So it seems that if there is an implicit conversion between different types, then the code compiles.

The issue also disappears when using a «normal» global variable, ie when using a namespace instead of a struct for scoping symbols. The static keyword, in this case, does not make any difference:

namespace c{
    const int i = 42;
}
int foo(const int& i) { return i; }

int main(){
    return foo(c::i);
}

Is there anything special happening with integral and fundamental types?

Apparently not, the same issue appears when using a user-defined type

struct wrap{int i;};
struct c{
    static constexpr const wrap i{42};
};
void foo(const wrap&) { }

int main(){
    foo(c::i);
}

Then notice that the following snippet compiles without issues

struct c{
    static const int i;
};
const int c::i = 42;
void foo(const int&) { }

int main(){
    foo(c::i);
}

and that it is not possible to rewrite this code with non-integral types, as constexpr requires an initializer where the value is declared.

I mainly analyzed the issue with a C++11 compiler (as it was the scope of the project), but then I decided to see if using a more recent C++ standard would change anything.

Indeed, in C++17, when using constexpr, the issue disappears, thus

struct wrap{int i;};
struct c{
    static constexpr const TYPE i{42};
};
void foo(const TYPE&) { }

int main(){
    foo(c::i);
}

compiles both with #define TYPE int and #define TYPE wrap with g++ --std=c++17 main.cpp.

But without constexpr it does compile only with #define TYPE int (as in C++11) and with optimizations.

Rule of thumb

This is another reason for using the tool designed to fulfill a particular job instead of using paradigms of other languages.

Also, for defining compile-time constants, use constexpr. It helps to avoid errors, see the issue with globals, avoid unnecessary overhead, and in this case, when targeting at least C++17, linker errors too.

But it does not explain why the code behaves differently when using struct, and it also does not offer a solution in those cases we should prefer it over a namespace.

Putting it all together

The issue does not appear if

  • initialization is not inline

  • there is a conversion

  • using constexpr in C++17 (and inline initialization)

  • there is no foo

  • an implicit conversion between the global and parameter required by foo happens

  • foo takes a parameter by value

Also note that for initializing inline, int suffices to be const, but other types (float, user-declared types, …​) need to be declared constexpr.

It seems that static member variables without out-of-class definitions are not real objects contrary to global variables.

I’m unsure where it is exactly stated in the standard and why this difference exists, but GCC and clang act accordingly.

The most explicit reference to this property I could find is the note

Once the static data member has been defined, it exists even if no objects of its class have been created. […​]

This also explains why the code compiles if the constant is an int and foo accepts a float. If a conversion takes place, then «a temporary object is created».

The fact that the code does compile with optimization is probably a side effect. The compiler might inline the function and completely optimize it away, or just replace the call with the literal value after it sees that the address is not taken.

In the case of function taking the parameter by value, the fact that the object does not exist is not an issue, as it is not possible to take the address of the global.

Having said that, following snippets compiles with both GCC and clang, which seems to contradict what I’ve just said

struct c{
	static const int i = 42;
};
int main(){
	&c::i;
}

But if the value is just stored somewhere

struct c{
	static const int i = 42;
};
int main(){
	auto p = &c::i;
}

then it does not compile with undefined reference to 'c::i', even if the value is used as much as before.

But when adding constexpr, why does the code compile since C++17?

inline variables

Since C++17 there are inline variables. Just as with inline functions, the inline keyword is used to avoid ODR-violations.

In this case, the object exists and thus there are no issues.

constexpr variables are, since C++17 inline, so I double-checked and

struct wrap{int i;};
struct c{
	static inline const TYPE i{42};
};
void foo(const TYPE& i) { }
int main(){
	foo(c::i);
}

compiles without constexpr and both with #define TYPE wrap and #define TYPE int.

Why is constexpr not necessary for int?

There is one question open, why don’t we need constexpr for int in the first example?

This is for historic reasons, as constexpr did not exist in the first revisions of the C++ language. Without special-casing int, writing

const int size = 3

void foo(){
	int arr[size]{};
}

would not have been possible.

For the record, in C the code is valid and uses a variable-length array (VLA), an array which stack size is determined at runtime. (making sizeof a runtime operation!).

In C++ the code is equivalent to

void foo(){
	int arr[3]{};
}

just as if size would have been an integral, and not a variable!

In C it is possible to mimic such feature with an enum or macro

enum{ size = 3 };
// or: #define size 3

void foo(){
	int arr[size]{};
}

And note that in both of those cases, there is no object size as it is not possible to get an address from it.

How to fix the linker error

There are multiple solutions, that only depend loosely on each other. Depending on the context some might not be applicable, and for each possible solution, one has to consider the pro and contra.

The best would be to use a never standard, at least C++17, and define the variable as constexpr. If the variable cannot be made constexpr

  • use C++17 and mark the variable constexpr

  • define the variable as inline

  • use a namespace instead of a struct/class

  • if the object is small, change foo to accept it by value

  • create a temporary object when the value is passed to foo. This can be done by creating a local variable, or with an apparently redundant static_cast. In the case of integral types, it can also be done by doing some otherwise unnecessary operations, like adding 0 or use the unary + operator on an integral type.

  • use an enum or define

  • leave the declaration as-is, and add a definition in the corresponding .cpp file. The initialization can be moved to the implementation file, but must be left in the header in case of constexpr.

Проблум со следующим кодом является статическим членом типа «const double» и не может иметь инициализатора в классе. Почему применимо только для const double в следующем коде? Пожалуйста, помогите мне.

class sample{
static const char mc = '?';
static const double md = 2.2;
static const bool mb = true;
};
const char sample::mc;
const double sample::md;
const bool sample::mb;
int main(){
}

27

Решение

Логика, реализованная стандартом языка C ++ 03, основана на следующем обосновании.

В C ++ инициализатор является частью объекта определение. То, что вы пишете внутри класса для статических членов, на самом деле только декларация. Таким образом, формально говоря, указание инициализаторов для любых статических членов непосредственно внутри класса является «неправильным». Это противоречит общим понятиям декларации / определения языка. Какие бы статические данные вы объявлять внутри класса должно быть определенный позже все равно. Вот где у вас будет возможность указать инициализаторы.

Исключение из этого правила было сделано для статических целочисленных констант, потому что такие константы в C ++ могут образовывать выражения интегральных констант (ICE). ДВС играют важную роль в языке, и для того, чтобы они работали должным образом, значения интегральных констант должны быть видны во всех единицах перевода. Чтобы сделать значение некоторой константы видимым во всех единицах перевода, оно должно быть видимым в точке декларация. Для этого язык позволяет указывать инициализатор непосредственно в классе.

Кроме того, на многих аппаратных платформах постоянные целочисленные операнды могут быть встроены непосредственно в машинные команды. Или константа может быть полностью исключена или заменена (как, например, умножение на 8 может быть реализован как сдвиг 3). Чтобы облегчить генерацию машинного кода со встроенными операндами и / или различными арифметическими оптимизациями, важно, чтобы значения интегральных констант были видны во всех единицах перевода.

Неинтегральные типы не имеют никакой функциональности, аналогичной ICE. Кроме того, аппаратные платформы обычно не позволяют встраивать неинтегральные операнды непосредственно в машинные команды. По этой причине вышеприведенное «исключение из правил» не распространяется на нецелые типы. Это просто ничего не достигнет.

45

Другие решения

Только до C ++ 11 const интегральные типы может быть непосредственно инициализирован в определении класса. Это просто ограничение, налагаемое стандартом.

С C ++ 11 это больше не применяется.

12

Компилятор предложил мне использовать constexpr вместо const:

static_consts.cpp:3:29: error: ‘constexpr’ needed for in-class initialization of static data member ‘const double sample::md’ of non-integral type [-fpermissive]
static_consts.cpp:7:22: error: ‘constexpr’ needed for in-class initialization of static data member ‘const double sample::md’ of non-integral type [-fpermissive]

Я только что принял предложение:

class sample{
static const char mc = '?';
static constexpr double md = 2.2;
static const bool mb = true;
};
const char sample::mc;
const bool sample::mb;
int main(){
}

И теперь он прекрасно компилируется (C ++ 11).

8

Hello,

I am trying to rosmake a package (modular_cloud_matcher) which forces -std=gnu++0x compiler option. This results in the following error:

/opt/ros/electric/stacks/geometry/tf/include/tf/tf.h:91:44: error: ‘constexpr’ needed for in-class initialization of static data member ‘DEFAULT_CACHE_TIME’ of non-integral type

and a similar one in tf2.

A possible solution is to change in tf.h from

static const double DEFAULT_CACHE_TIME = 10.0;

to

static constexpr double DEFAULT_CACHE_TIME = 10.0;

I suppose, however, this is not the right way to solve the issue, because next update will remove my modification. I am rather new to the ROS community, so I would like to know what should be done in such cases.

3 Answers

When you find a compiler problem like this, please open a defect ticket for the appropriate ROS component (in this case geometry). Mention the exact system version and compiler (i.e. gcc --version) you use.

In this case, constexpr is a new feature of the recent C++11 standard, which is not supported for any current ROS distribution. As REP-0003 states:

  • «Use of C++[0|1]x or tr1 features are only allowed if support for that feature is checked at compile time, and equivalent functionality exists without requiring C++[0|1]x code. A wholesale jump to C++[0|1]x will not happen until all commonly used OS platforms fully support it.»

So, while you should submit your patch with the defect ticket, the full solution will need to be somewhat different, to support all the other ROS platforms.

Technically, the old C++ standard says that non-integer constant data is
illegal in class declarations. The easy way to fix this is to define it
in the source file. I fixed it by moving the definition of
DEFAULT_CACHE_TIME to tf.cpp and it now works with GCC 4.4.5 on Debian
6.0.4, no —std argument needed. I’m pretty sure it will also work in C++0x. constexpr is more for template metaprogramming and places
where you can’t separate the definition from the declaration, or would
like to make something constant for performance tuning.

Hello,

I am the maintainer of modular_cloud_matcher, and while I’m aware of ROS’ repulsion of the use of C++0x, I decided to use it nevertheless because it made the code much cleaner and worked for the main ROS platforms starting from GCC 4.4 (Ubuntu Lucid). I did not use any features not explicitly supported and tested with GCC 4.4. Moreover, I think that it is good people begin testing ROS with C++0x as it will help with the eventual transition.

I did not expect too much problems such as this one to happen, as C++0x is supposed to be highly backward compatible, but it does happen sometimes.

I have created patches that fix these issues, see the related ticket

Your Answer

Please start posting anonymously — your entry will be published after you log in or create a new account.

Question Tools

1 follower

Related questions

  • Forum
  • Beginners
  • Just one string member shared by all obj

Just one string member shared by all objects

With this, with each new objetct i’m creating an array of four pointers to four literal char strings:

1
2
3
4
class Seasons
{
    const char * const SEASONS[4] = {"Spring","Summer","Fall","Winter"};
};

But i want just a static array of pointers shared by all class objects, like this:

1
2
3
4
class Seasons
{
    static const char * const SEASONS[4] = {"Spring","Summer","Fall","Winter"};
};

But it doesn’t compile, here’s the error, and i’m using C++11:

‘constexpr’ needed for in-class initialization of static data member ‘const char* const Seasons::SEASONS [4]’ of non-integral type [-fpermissive

Last edited on

constexpr static const char * const SEASONS[4] = {"Spring","Summer","Fall","Winter"};

Thank you Repeater, it’s compiling.

But now, why this work:

1
2
3
4
Seasons::Seasons()
{
    cout << "ESTACION: " << SEASONS[0] << endl;
}

And this doesn’t work:

1
2
3
4
5
Seasons::Seasons()
{
    int INDEX = 0;
    cout << "ESTACION: " << SEASONS[INDEX] << endl;
}

C++14: If a constexpr static data member is odr-used, a definition at namespace scope is still required, but it cannot have an initializer.

C++17: If a

static data member is declared constexpr, it is implicitly inline and does not need to be redeclared at namespace scope. This redeclaration without an initializer (formerly required) is still permitted, but is deprecated.

http://en.cppreference.com/w/cpp/language/static

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>

struct Seasons {
    
   Seasons();
   private: static constexpr const char * const season_names[4] = { "Spring", "Summer", "Fall", "Winter" };
};

// C++14: this definition is required, but it cannot have an initializer.
// C++17: this definition is permitted, but it is not required (it is deprecated)
// comment out the following line, and with -std=c++14, there would (or at least should) be an error
// (note: the GNU compiler does swalow this without choking, even in strict C++14 mode. 
//        AFAIK, it is non-conforming in this regard.)
constexpr const char * const Seasons::season_names[4]; // this was required before C++17
 
Seasons::Seasons() {
    
    int INDEX = 0;
    
    // the static data member is odr-used in the following line
    std::cout << "ESTACION: " << season_names[INDEX] << 'n' ;
}
 
int main() {
    
    Seasons s ;
}

http://coliru.stacked-crooked.com/a/68dc515e47fd13e8

Topic archived. No new replies allowed.

Понравилась статья? Поделить с друзьями:
  • Error connect etimedout postman
  • Error connect ehostunreach
  • Error connect econnrefused postman
  • Error connect econnrefused mongodb
  • Error connect econnrefused 3306