6

I have this piece of generated code that maps ints to ints, whose core is a simple table. In pre C++17, it used to look like this:

int convert (int v) { static const int table[] = { 3, 2, 6, 1, 7, 1, 6, 8 }; if (0 <= v && v < sizeof table / sizeof table[0]) return table[v]; else return -1; } 

with C++17, I would like use constexpr. I expected that adding constexpr to the function signature would suffice, but I have to remove the static of the table, which makes my implementation more complex for apparently no good reason. Not too mention that table in non constexpr context will probably be on the stack, so I guess I should replace static by constexpr.

G++ 8 reports:

/tmp/foo.cc: In function 'constexpr int convert(int)': /tmp/foo.cc:14:26: error: 'table' declared 'static' in 'constexpr' function static const int table[] = { 3, 2, 6, 1, 7, 1, 6, 8 }; ^ 

and Clang++ 7:

/tmp/foo.cc:14:20: error: static variable not permitted in a constexpr function static const int table[] = { 3, 2, 6, 1, 7, 1, 6, 8 }; ^ 1 error generated. 

Since I want this piece of code to work with all the C++ standards (and do the right thing in each case), I think I have to write this (yeah, macros, that's not the question):

#if 201703L <= __cplusplus # define CONSTEXPR constexpr # define STATIC_ASSERT static_assert # define STATIC_OR_CONSTEXPR constexpr #else # include <cassert> # define CONSTEXPR # define STATIC_ASSERT assert # define STATIC_OR_CONSTEXPR static #endif CONSTEXPR int convert (int v) { STATIC_OR_CONSTEXPR const int table[] = { 3, 2, 6, 1, 7, 1, 6, 8 }; if (0 <= v && v < sizeof table / sizeof table[0]) return table[v]; else return -1; } int main() { STATIC_ASSERT(convert(-42) == -1); STATIC_ASSERT(convert(2) == 6); STATIC_ASSERT(convert(7) == 8); STATIC_ASSERT(convert(8) == -1); } 

So:

  • what motivates the interdiction to have static-storage variables in constexpr functions?

  • is there a cleaner alternative I might have missed? Sure, I can pull table out of convert, but I would like to avoid that.

  • does the standard guarantee that const arrays in constexpr functions in non-constexpr contexts will be in static storage instead of the stack?

2
  • 1
    One question at a time please. Commented Dec 27, 2018 at 7:27
  • Note there are no restriction change in constexpr function, so by adding comment in your static_assert, your macro can start at C++14. Commented Dec 27, 2018 at 8:45

2 Answers 2

3

Edit: My previous answer was just flat out wrong ... so I'm going to fix that!

I know I'm very late to this and I'm sure you have since worked out everything I have to say.

Uortunately, you cannot have static variables inside a constexpr function.

The C++14 standard states that constexpr function bodies cannot contain "a definition of a variable of ... static or thread storage duration". This makes sense as a constexpr constext is evaluated outside of those runtime constructs.

However we're using C++ and not C and that means we have objects! Objects can have constexpr static storage! My solution would be to wrap the function (as a static function) in an object/type which contains the data as a constexpr static member.

I understand static variables can lead to initialization problems in the non-constexpr world, so you may need to use the preprocessor to select a working version based on the standard you're compiling with.

With this, the C++14 code becomes something like:

class convert { static constexpr int table[] = {3, 2, 6, 1, 7, 1, 6, 8}; public: static constexpr int run(int v) { if (0 <= v && v < sizeof table / sizeof table[0]) return table[v]; else return -1; } }; 

You may also want to add a static constexpr int/unsigned int/size_t to specify the length of the array to remove the ugly sizeof stuff you have to do (although if you're targeting multiple standards then this might not be viable).

You could instead specify this as a template parameter which (I believe) should work on all standards!

As for you last question. I'm pretty sure that any non-static constexpr object inside a constexpr function will be initialized on the stack in non-constexpr contexts (a little bit of testing on Compiler Explorer seems to be in line with this). The main difference is that the resulting value will just be constructed right out of the gates with "no" runtime cost (the reason for constexpr's existance).

I feel like this is closer to what you had originally than the other answer and templates can be a pain to get your head around sometimes (although I would personally go with the variadic template method for generating the array)

See https://en.cppreference.com/w/cpp/language/constexpr and Does static constexpr variable inside a function make sense? for more info!

Sign up to request clarification or add additional context in comments.

3 Comments

If the length is static const then you should be fine with all standards. It's a reasonable approach. Though std::size (or some pre-C++17 stand-in implementation, which is trivial) would be easier.
True there are better constructs for determining array length in C++17. I personally haven't come across a scenario where I felt that I gained anything by using them. Its 1 int after all! (although I tend to perfer to build my arrays from a variadic factory of some kind so that's all done automatically anyway)
It's an int you can forget to change. Every line of code you write is a potential failure point unless you do so robustly.
1
  • is there a cleaner alternative I might have missed? Sure, I can pull table out of convert, but I would like to avoid that.

How about (C++11 compatible):

template <int... Ns> struct MyTable { static constexpr int get(std::size_t v) { return v < sizeof...(Ns) ? data[v] : -1; } static constexpr int data[] = {Ns...}; }; constexpr int convert (std::size_t v) { using table = MyTable<3, 2, 6, 1, 7, 1, 6, 8>; return table::get(v); } 

Demo

  • does the standard guarantee that const arrays in constexpr functions in non-constexpr contexts will be in static storage instead of the stack?

I don't think that constexpr variable need to be materialized, turning that code in switch (or perfect hash) seems valid implementation.

So no guaranty I think.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.