single expression helper for compile-time enforced

2019-01-18 07:33发布

问题:

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

回答1:

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.



回答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.



回答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