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 int
s), 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.)
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?
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With