Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to automatically define a variable static or non-static depending on whether the initialization is constexpr?

Tags:

c++

c++17

Suppose that I have a type:

struct Foo {
    int a, b, c, d, e;
};

Now, I'd like to have a macro (or any other solution), which can define a Foo object in a way, that if the object can be constexpr (because it is initialized with compile-time ints), then it defines it as static constexpr Foo. If cannot be constexpr, then it defines as const Foo (I'd use this macro in function scope).

So, I'd like to have a macro (or some equivalent solution):

#define DEF(a, b, c, d, e) ... // magic here

If I call it with compile-time constants:

DEF(1, 2, 3, 4, 5);

Then I'd like this to expand to:

static constexpr Foo foo{1, 2, 3, 4, 5};

But if any of the parameters are not compile-time constants (so it cannot be constexpr):

int b = 2;
DEF(1, b, 3, 4, 5); // second parameter is not a compile-time constant

then I'd like to have:

const Foo foo{1, b, 3, 4, 5};

The reason that I'd like to have something like this is that compilers are not allowed to optimize away foo from stack, so I have to do this optimization manually.

(Note, that I use Foo a lot of places, that's why I'd like to have an automatic solution. Currently I need to decide whether foo should be static or not case-by-case, which is tedious.)

like image 892
geza Avatar asked Nov 17 '18 14:11

geza


People also ask

Does constexpr have to be static?

A static constexpr variable has to be set at compilation, because its lifetime is the the whole program. Without the static keyword, the compiler isn't bound to set the value at compilation, and could decide to set it later. So, what does constexpr mean?

Can constexpr functions call non constexpr functions?

A call to a constexpr function produces the same result as a call to an equivalent non- constexpr function , except that a call to a constexpr function can appear in a constant expression. The main function cannot be declared with the constexpr specifier.

Is static variable initialized only once?

Static variables are initialized only once. Compiler persist the variable till the end of the program. Static variable can be defined inside or outside the function. They are local to the block.

What is constexpr Auto?

constexpr is a new C++11 keyword that rids you of the need to create macros and hardcoded literals. It also guarantees, under certain conditions, that objects undergo static initialization.


1 Answers

I know you mentionned in the comments that __builtin_constant_p is not ok for you as you want a portable solution, but in case someone else stumbles upon this question, it can definitely be used to achieve this:

By combining decltype(auto), automatic lambda capture and temporary lifetime extension, we can do the following:

struct Foo {
    int a;
    int b;
    int c;
};

void worker(const Foo*);

#define DEF(A, B, C)                            \
  [&]() -> decltype(auto) {                     \
        constexpr bool test =                   \
          __builtin_constant_p(A) &&            \
          __builtin_constant_p(B) &&            \
          __builtin_constant_p(C);              \
                                                \
        if constexpr(test) {                    \
            static const Foo res {A, B, C};     \
            return (res);                       \
        }                                       \
        else {                                  \
            return Foo{A, B, C};                \
        }                                       \
    }()                                         \
    //end of macro

void foo(int v) {
    const Foo& x = DEF(1, 2, 3);
    //const Foo& x = DEF(1, v, 3);

    worker(&x);
}

Which generates the correct code in both scenarios. See on godbolt

If someone could come up with some crafty SFINAE sheananigans to replace __builtin_constant_p with something portable in this context, you would be in business.

Explanation: The real key here is the temporary lifetime extension. The reasoning is that having a macro spit out the static keyword would be a massive headache, so let's just not bother with it!

A const Foo& is perfectly capable of pointing to a static const, and as long as the regular Foo is built as a temporary, lifetime extension will (for every intent and purpose) promote the reference into a regular variable during compilation. Also, remember that references do not have addresses of their own, so the problem explained in your linked question does not apply to them.

With decltype(auto), we can then create a function that can return either a temporary Foo or a const Foo& that will populate that const reference.

Finally, packaging this in a lambda (as opposed to helper functions/templates) allows us to easily distinguish literals versus named variables, and allows the compiler to establish for certain that the static variable gets initialized using constants expressions. This is important because a bunch of thread-safety boilerplate will get tacked on at the slightest hint of ambiguity.

like image 200
Frank Avatar answered Sep 30 '22 20:09

Frank