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

Viewed 271

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.)

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.

Related