Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Static table inside constexpr function

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?

like image 933
akim Avatar asked Oct 16 '22 09:10

akim


1 Answers

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!

like image 166
Tom Hickson Avatar answered Nov 04 '22 00:11

Tom Hickson