0

constexpr might run functions at compilation time. Is there a way to force it to compilation time only?

Sample code:

constexpr int BUILD(int i)
{
    static_assert(0 == i);
    i++;
    return i;
}


enum Events
{
    FIRST = BUILD(0)
};

The compilation error:
Error[Pe028]: expression must have a constant value

[Edit] Another example to explain the rationale:

constexpr int BUILD(int a, int b, int c, int d)
{
    static_assert(a < b);
    static_assert(b < c);
    static_assert(c < d);
    static_assert(d < 10);
    return a+b+c+d;
}


enum Events
{
    FIRST = BUILD(0, 4, 6, 9),   //numbers are defined manually
    SECOND = BUILD(2, 3, 7, 8),
    THIRD = BUILD(0, 1, 2, 3),
};
Ronen333
  • 69
  • 1
  • 8
  • 2
    What's the point of the `static_assert`? If I remove it, it compiles for me. – Rulle Nov 28 '22 at 08:45
  • 2
    You can make a function compile-time only with `consteval`. However, you can still not validate a parameter this way. – BoP Nov 28 '22 at 08:45
  • @Rulle that's the idea. Think of a complex build that is built half-manually. The `assert` prevents human error – Ronen333 Nov 28 '22 at 08:53
  • 2
    @Ronen333 Can you give us *the bigger picture* of the actual problem that you are solving? Then we could give better help. – Rulle Nov 28 '22 at 08:56
  • @BoP unfortunately it doesn't run at C++14 – Ronen333 Nov 28 '22 at 08:57
  • @Rulle added another example at the post – Ronen333 Nov 28 '22 at 09:00
  • 2
    why do you want to perform those checks via passing arguments to funcitons? – 463035818_is_not_an_ai Nov 28 '22 at 09:04
  • @463035818_is_not_a_number added a new example. The idea is to build a complex `enum` at compilation time – Ronen333 Nov 28 '22 at 09:05
  • 2
    ok, but the issue is that function parameters arent constant expressions, hence you cannot static assert them. Trying to call the function at compile time is your problem not your solution ;) Its a bit of a [xy problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem), though both x and y are somewhat clear – 463035818_is_not_an_ai Nov 28 '22 at 09:08
  • @Ronen333 The short answer is that you can probably replace `static_assert` by an ordinary `assert`. – Rulle Nov 28 '22 at 10:02

5 Answers5

3

Function parameters are not constant expressions, hence you cannot static assert on their value.

You can use a template instead. Just to outline the idea, this is one possibility to provoke an error when i is not 0:

template <int i, bool = true>
struct BUILD;

template <int i>
struct BUILD<i, i==0> { static constexpr int value = 0;};

enum Events
{
    FIRST = BUILD<0>::value,
    ERROR = BUILD<1>::value
};

Here FIRST is ok, while ERROR triggers:

<source>:15:23: error: incomplete type 'BUILD<1>' used in nested name specifier
   15 |     ERROR = BUILD<1>::value
      |                       ^~~~~
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
1

There's no such a thing in C++ as a constexpr function parameter. It doesn't matter if you pass a constexpr argumen t to a function, e.g.

constexpr int i = 3;
f(3)

the function f, the signature of which could be

void f(int x)

will not know whether the parameter x is bound to a constexpr argument or not, so you'll not be able to use x in static_assert.

If you really want to pass compile time args, use metafunctions. Roughly speaking, you change a function taking some (runtime) arguments and returning some value to a class templated on corresponding template arguments and containing a static constexpr member resolving to the value you want to compute; the body of the function, where you want to assert, becomes the body of the class; then you can also have a template bool variable to ease the usage.

So a function like

int BUILD(int i)
{
    /*runtime*/assert(0 == i);
    i++;
    return i;
}

can become

template<int i>
struct BUILD_impl {
    static_assert(0 == i);
    constexpr static bool value = i + 1;
};
template<int i>
constexpr bool BUILD = BUILD_impl<i>::value;

now you'd use BUILD<0> instead of BUILD(0).

Enlico
  • 23,259
  • 6
  • 48
  • 102
1

You cannot use the the parameter of a function in a static_assert, regardless of its modifiers.

I recommend going with a template instead, for similar syntax:

namespace Impl
{

constexpr int CalculateBuild(int a, int b, int c, int d)
{
    return a+b+c+d;
}

template<int a, int b, int c, int d>
struct BuildHelper
{
    static constexpr int value = CalculateBuild(a,b,c,d);

    static_assert(a < b, "a < b violated");
    static_assert(b < c, "b < c violated");
    static_assert(c < d, "c < d violated");
    static_assert(d < 10, "d < 10 violated");
};

template<int a, int b, int c, int d>
constexpr int BUILD = BuildHelper<a,b,c,d>::value;

}

using Impl::BUILD;

enum Events
{
    FIRST = BUILD<0, 4, 6, 9>,   //numbers are defined manually
    SECOND = BUILD<2, 3, 7, 8>,
    THIRD = BUILD<0, 1, 2, 3>,

    // ERROR = BUILD<1, 2, 3, 10>

    // uncommenting the above yields something like
    //
    // static_assert failed: 'd < 10 violated'
    //
    // along with some information about which template specialization is responsible
};

fabian
  • 80,457
  • 12
  • 86
  • 114
1

C++20 introduced immediate functions, that must be evaluated at compile time to produce a compile-time constant1.

We can also throw an exception from there, which results in a compilation error:

consteval int BUILD(int a, int b, int c, int d)
{
    if ( not (a < b) ) throw "a should be less than b";  
    if ( not (b < c) ) throw "b should be less than c";  
    if ( not (c < d) ) throw "c should be less than d";  
    if ( not (d < 10) ) throw "d should be less than 10";  

    return a+b+c+d;
}

enum Events
{
    FIRST = BUILD(0, 4, 6, 9), 
    SECOND = BUILD(2, 3, 7, 8),
    THIRD = BUILD(0, 1, 2, 3),
//    ERROR_A = BUILD(5, 4, 6, 9),
//    ERROR_B = BUILD(2, 7, 3, 8),
//    ERROR_C = BUILD(0, 1, 4, 3),
//    ERROR_D = BUILD(0, 1, 2, 13)
};

So that, uncommenting say, the C case, we get an error2 like:

<source>:18:15: error: expression is not an integral constant expression
    ERROR_C = BUILD(0, 1, 4, 3),
              ^~~~~~~~~~~~~~~~~
<source>:5:24: note: subexpression not valid in a constant expression
    if ( not (c < d) ) throw "c should be less than d";  
                       ^
<source>:18:15: note: in call to 'BUILD(0, 1, 4, 3)'
    ERROR_C = BUILD(0, 1, 4, 3),
              ^
1 error generated.

The same works in C++14, using a constexpr function3.


1) See e.g. https://en.cppreference.com/w/cpp/language/consteval or What is consteval?
2) https://godbolt.org/z/Goc7YrYWr
3) https://godbolt.org/z/8b4GqG467

Bob__
  • 12,361
  • 3
  • 28
  • 42
0

As suggested in Asserts in constexpr functions, you can use assert instead of static_assert in constexpr functions. This works, because only if the assertion fails will a function that is not constexpr be called, causing a compilation error.

Or you can implement your own assert my_constexpr_assert that is always active whether debug mode is turned on or off:

void my_assert_fail() {} // NOT Constexpr
#define my_constexpr_assert(expr) if (!(expr)) {my_assert_fail();}

The following compiles for me:

constexpr int BUILD(int a, int b, int c, int d) {
  my_constexpr_assert(a < b);
  my_constexpr_assert(b < c);
  my_constexpr_assert(c < d);
  my_constexpr_assert(d < 10);
  return a+b+c+d;
}

enum Events {
  FIRST = BUILD(0, 4, 6, 9),
  SECOND = BUILD(2, 3, 7, 8),
  THIRD = BUILD(0, 1, 2, 3)
};

but if I edit a line to be THIRD = BUILD(0, 1, 0, 3) I get the error

main.cpp:2:63: error: call to non-‘constexpr’ function ‘void my_assert_fail()’
    2 | #define my_constexpr_assert(expr) if (!(expr)) {my_assert_fail();}
      |                                                 ~~~~~~~~~~~~~~^~
main.cpp:6:3: note: in expansion of macro ‘my_constexpr_assert’
    6 |   my_constexpr_assert(b < c);

which is what we expect, claiming that assertion b < c failed.

You can replace my_constexpr_assert by assert from <cassert> but I am not sure it will be active when debug mode is turned off.

Note: You may want to put an abort(); in my_assert_fail to make sure that it produces an error even when called from a non-constexpr function.

Rulle
  • 4,496
  • 1
  • 15
  • 21