single expression helper for compile-time enforced constexpr function evaluation possible?
Asked Answered
B

4

5

@cyberpunk_ is trying to achieve something and made some questions about it but all the chase boils down to this:

Is it possible to build a tool to enforce compile-time evaluation of a constexpr function?

int f(int i) {return i;}
constexpr int g(int i) {return i;}

int main()
{
    f(at_compilation(g, 0));
    int x = at_compilation(g, 1);
    constexpr int y = at_compilation(g, 2);
}

In all situations, at_compilation enforce compilation-time evaluation of g.

at_compilation doesn't need to be in this form.

Requirements

  • Allow any (numerical native) literal type as input for the constexpr function.
    • this could also be hardcoded based on the function arguments types.
  • Allow any (numerical native) literal type as output, which is the result of the constexpr function call.
    • this could also be hardcoded based on the function return type.

Desirables

  • Reduced macro usage but don't be afraid of using.
  • Be general (not type hardcoded).
  • Support any literal type. At last any numerical native literal type is a requirement.

Related Questions:

  1. When does a constexpr function get evaluated at compile time?
  2. Forcing a constant expression to be evaluated during compile-time?
  3. Passing any function as a template parameter?
  4. Where in the C++11 standard does it specify when a constexpr function can be evaluated during translation?

Answers with relevant code samples:

  • 1
  • 2
  • 3 (this one has an illustrative AT_COMPILATION macro)

All the code samples have limitations regarding the requirements.

A clear explanation for how this is unfeasible in C++ is also a good answer.

I suspect it's impossible based on @K-ballo / @Herb Sutter answer which states "and the result is used in a constant expression as well". This was not part of my former conception about constexpr functions, I firstly thought that just passing literals (or other compile-time input) as arguments would suffice to guarantee (by standard) it to be evaluated at compilation-time.

It's already assumed constexpr function's purpose is that they can fit in constant expression situations when necessary, like in array bounds. That's OK. Given that, this question is about a hack on using them just as a tool for compile time calculation. Whether it's a good or bad thing to do should not matter.

Bambi answered 13/1, 2013 at 23:1 Comment(7)
I do not think it is possible. A constexpr will be evaluated at compile-time based not only on its arguments but on the context where its used, and there are no context-dependent expressions in C++.Ottavia
For any purported compile-time constant integer expression f(), you can always use typedef char dummy[f()]; and then get the value as sizeof(dummy) or std::extent<dummy>::value.Cilicia
@KerrekSB: the value must be positive...Hayashi
@aschepler: That's true. I guess adding suitable casts involving make_unsigned and decltype could work around that limitation...Cilicia
Why the lambda trick is not good enough? It's impossible without macro anyway.Evans
@zch, It fails at constexpr int y = at_compilation(g, 2). That trick blocks chaining of constexpr when desired.Bambi
Even if the compiler evaluated the expression at compile-time, it is usually still allowed to re-evaluate it at runtime (and check that 2+2!=3 every other instruction, for fun).Helicline
C
3

I believe that it's impossible because the compiler is only required to compute values that are used at compile-time, and there is no generic expression that can use every part of a value of class type. Computations that initialize private members might even be impossible to force, as you would depend on a public constexpr member function to use the result.

If you could access the object representation by

static_cast< char const * >( static_cast< void const * >( & const_value ) )

then it would be possible to checksum the result of the computation (and use the result as an integral constant expression), forcing the compiler to perform every calculation that isn't moot. But the cast from void * to char * is disallowed in a constant-expression, and likewise attempting to accomplish the same with a union. Even if it were allowed, if the constructor left one byte uninitialized, using an uninitialized value is also forbidden in a constant-expression.

So, even if C++ had better tools for introspection, it would still be impossible to recover the work performed by a constexpr function in order to artificially use some members but not others.

Just to be clear (even if it repeats the question), there's no reason to want this. The language already requires a check that everything can be computed at compile time, if needed, and the only effect of forcing the compiler to non-lazily compute pure values would be to make it slower and use more memory.

Edit (question was radically altered)

If you have several functions returning scalar type, and want to ensure that some of them work as constant expressions under certain arguments, then write test cases using static_assert.

constexpr int g(int i) {return i;}
int i = 5;
static_assert( g( 3 ) == 0, "failure 1" );
static_assert( g( i ) == 5, "failure 2" );

If you don't want to fix the result values, then discard them. (Unfortunately, GCC may optimize out the non-constant part of such an expression, so you might need to do something more baroque on that platform.

static_assert( g( i ) == 5 || true, "failure only if not constexpr" );

As for encapsulating this into a macro, the other linked questions seem to address a lot. If you want to expand one of those answers or to fix a particular bug, it would be better to explain the bug rather than ask us to read so much literature and start from scratch.

Corbett answered 14/1, 2013 at 4:30 Comment(28)
Although it has a few points that may matter for the actual question, I see it has multiples points that don't. I see too much thing that is present for not directly addressing from where the possibility of it breaks down (given all the constraints present). That's the only thing that matters (at last for me, and I guess in a strict way too).Bambi
@chico 1. The question only makes sense if we define what "function evaluation" means. In this context that means using all the values computed by a function. 2. Can we use all the values contained by an object of class type? 3. The strategies that work at runtime don't work at compile time. 4. No more possibilities; it's impossible. — If you feel something is left unanswered, or have more questions, please ask. If you see a shortcut to cut this answer down, please write your own answer.Corbett
What's the purpose of constexpr functions? They fit both in runtime as in compile time, it's a strategy that can be employed to initialize a ordinary variable at runtime as for initializing an array size, provide you know the constraints that apply for the arguments. The question asks for that, the constraints are already assumed for the arguments, it's just about providing all compile time constraints while packaging it to resemble the syntax that's asked for. It's just that. Given that, I don't see the point of your wording, maybe I need to rephrase the question.Bambi
@chico Rephrasing the question is the problem. When I <s>read</s> answered it, it didn't mention "numerical native". Yes, you can certainly evaluate and discard a scalar number at compile time: static_assert( g(0) == 0 || true, "" ); Is that seriously all?Corbett
If you had saw my answer, the illustrative AT_COMPILATION macro, you may have got a better idea of this craziness =)Bambi
constexpr does a lot more than initialize array bounds from expressions with functions. You can initialize an entire compile-time hierarchical data structure, and compile-time navigate it to generate other things including switch statements, statically-initialized data, and array bounds.Corbett
@chico If it's essential to the question, it should be given and explained, not linked from a footnote. And I don't see the point of it. It's a convenience wrapper that doesn't add anything semantically to just writing out a function call. If you want to ensure that something is compile-time, for testing purposes, then static_assert is the correct tool.Corbett
All nice, but wanting to make constexpr functions to eval at compile time just with a short call syntax seems a complete impossibility. You aways need to set a constexpr variable in a previous line. That's what @cyberpunk_ was trying to eliminate, and I just got curious george about it.Bambi
@chico No need to define a constexpr variable, pass a literal! If you have a specific bug in our AT_COMPILATION macro, then put the source code into a dedicated question and describe the bug. Don't get all theoretical and make an XY problem, then expect a specific fix for AT_COMPILATION. Ask a theory question, get a theory answer.Corbett
I'm sure I've not radically changed the answer, just reduced the type scope a bit more. From my understating, you seem to have interpreted I was asking about evaluation of any function, but I've warned this was about constexpr function from beginning, and as such, a lot of constraints apply. They're for compile time evaluation already.Bambi
Also static_assert is not the aim, just enforce by enforcing is not the aim, the purpose is to have the return to print or do anything with it. Also, about just pass the literal... I advice to read all the links I provide, including comments. Question 1, both at isocpp as in stackoverflow is crucial.Bambi
@chico constexpr applies to objects and return values and class type, and the types to which it applies are called literal types. It now appears coincidental that you used such terminology to describe what you wanted, but how was I to know that? You reduced the scope so much as to make it a completely different question… and you still need to reduce the scope further by posting AT_COMPILATION and describing what you want it to do. Providing these extensive references is not part of the Q&A process, and we're not responsible for reading all those pages to divine your real question.Corbett
I think it's good enough as it is, you can downvote it because of that if you think it deserves it =)Bambi
s/to have the return/to have the constexpr function return/Bambi
I guess you won the big prize. Thanks, @cyberpunk_ will like this.Bambi
Hey, || true couldn't induce an optimization that would even remove o out of the analyzes? Also even the former expression couldn't be optimized to just true?... I'm unsure whether this can or not happen and so eliminate its usefulness.Bambi
nice try =), but enforce_constexpr must be constexpr itself for being able to initialize a constexpr variable.Bambi
hmmm, I first tried clang at my machine, I guess we found a compiler error, liveworkspace.org/code/1t5Gpo$0. I thought the code was ok, the error was pointing for something that at instantiation should be ok, and it's with gcc indeed, but I'm still unsure about the analysis elimination by aways true.Bambi
ok, liveworkspace.org/code/1t5Gpo$1, it seems gcc is doing the elimination thing in truth... no compiler is working with that, for the purpose. And I'm unsure whether it should be assumed to work.Bambi
@chico hmm, it works with o == 0 || o != 0. Looks like a compiler bug to me.Corbett
it doesn't work, gcc is still passing without checking when using a runtime value and clang presents the same error.Bambi
@chico -O2 seems to trigger it. Unfortunately, this puts you into an arms race with the optimizer. Take comfort in the idea that you're right, and it's wrong… compiling for debug with -O0 is fairly reasonable, anyway.Corbett
Guys, go to the chat for further discussion, please!Granduncle
@chico I think he's assuming the asker still wants the function to work on any arguments, like any normal function. Doing it that way defeats the goal of checking that the argument is a constant expression. Note that I'm not trying to make the static_assert fire, the diagnostic is that the static_assert itself is invalid.Corbett
@Potatoswatter, It's invalid to use the value of a constexpr function argument in a static_assert, clang diagnostic is right, It doesn't matter if I call enforce_constexpr, defining it is illegal.Bambi
@chico Ah, actually you're right. A constant expression is only produced after the values of arguments are substituted into the function. At the time the function is defined, the expression is not a constant expression and should directly fail.Corbett
@Corbett it's like you're expecting your function to work like a macro, or like it has to have an instantiation for each argument value it's used with. It doesn't work like that. A constexpr function argument is an argument that can or not be constexpr. So it's invalid from this point upfront.Bambi
@chico Macros don't evaluate anything; they just substitute text. I was treating it like a template. constexpr functions do have instantiations in a sense, in that they're memoized, although that's not part of the standard. Anyway, you're probably best off adapting the macro you already wrote.Corbett
K
3

Thanks to C++17 (lambda constexpr, auto template parameter, inline as valid template non-type value) we now have a solution:

//implementation
#include <utility>

template<auto X>
using constant = std::integral_constant<decltype(X), X>;

template<class T>
constexpr auto to_constant(T f) //should use && but clang has a bug that would make +f fail
{
   constexpr auto ptr = +f; //uses conversion operator to function pointer
   return constant<ptr>{}; //not yet implemented for gcc ("no linkage"), working with clang
}    

#define constexpr_arg(...) to_constant([]{ return __VA_ARGS__; })

//userland
template<auto Func>
constexpr void func(constant<Func>)
{
   constexpr decltype(auto) x = Func();
   static_assert(x == 3.14);
}

int main()
{
   func(constexpr_arg(3.14));
}

proof it's working : https://godbolt.org/g/vWbyjE

Also this version doesn't work for all cases (mainly if the argument of the macro uses non-constexpr values but still produce a constexpr result).

For such uses cases : https://godbolt.org/g/DRZ5JM

For a gcc version (so portable for now):

//implementation
template<class T>
struct constant
{
   static constexpr decltype(auto) value = T::getPtr()();
};

template<class T>
constexpr auto to_constant(T&& f) //remove the && if you want to be also compatible with clang
{
   constexpr auto ptr = +f; //uses conversion operator to function pointer
   struct A
   {
      static constexpr auto getPtr() { return ptr; }
   };
   return constant<A>{};
}    

#define constexpr_arg(...) to_constant([]{ return __VA_ARGS__; })

//userland
template<class Constant>
constexpr void func(Constant&&)
{
   static_assert(Constant::value == 3.14);
}

int main()
{
   func(constexpr_arg(3.14));
}

https://godbolt.org/g/LBCYfi

Kasi answered 3/11, 2017 at 4:19 Comment(6)
Interesting code!, but sadly I think there may be a misunderstanding. "userland" shouldn't have a special function signature for the expressions being passed as arguments, so that they are enforced to evaluate at compile time, by extraction, etc. For example, for void foo(int), one wishes to enforce a compile time constant to be calculated when passing to foo in foo(eval_at_compilation_time(constexpr_bar(1000))). If eval_at_compilation_time(constexpr_bar(1000)) can't be evaluated at compilation time, an error should be generated instead of letting it for runtime.Bambi
Ah ya, I misread the question. The solution is much simpler then: godbolt.org/g/qT5W89Kasi
Of course, foo([]{ constexpr auto e = expr; return e; }()) works! Does it work only now, on C++17, due to constexpr lambda? This succinct oneliner can be the full answer, no need for the macro helper, etc.Bambi
Notice if you follow the question links, in the previous quest, it was indeed attempted to make use of lambdas for this but without luck.Bambi
This didn't work in C++14 at the time of this question. The same solution was attempted before. It is now working behind the -std=c++17 flag, notice that with -std=c++14 it still doesn't, it's due to the fact that lambdas now can be used in constexpr context, so it can now be enforced without missing the constexpr characteristic in the final result.Bambi
actually foo([] -> decltype(auto) {constexpr decltype(auto) e = expr; return e; }()) would be a more accurate answer I believe.Kasi
B
2

Use std::integral_constant:

int x = std::integral_constant<int, g(0)>::value;
f(std::integral_constant<int, g(1)>::value);

This code will not compile if g(n) is not evaluated at compile-time.

Bug answered 18/4, 2013 at 2:27 Comment(1)
doesn't work for floating points, this solution is already linked in the question.Bambi
E
0

Here are two more solutions that haven't been provided yet. Both rely on C++17 constexpr lambdas.

In both cases, we define a macro CONSTEVAL(expr) where expr is an expression that will be constant evaluated.

C++11 constexpr variable in lambda

#define CONSTEVAL(...) [] { constexpr auto _ = __VA_ARGS__; return _; }()

This solution is taken from @Justin's answer to another question.

C++20 update

#define CONSTEVAL(...) [] consteval { return __VA_ARGS__; }()

This solution is conceptually similar to the one above; it's just slightly simplified.

Edie answered 14/9, 2023 at 18:5 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.