In C++11 constexpr functions, a second statement such as an assert()
is not possible. A static_assert()
is fine, but wouldn't work if the function is called as 'normal' function. The comma operator could come to help wrto. the assert()
, but is ugly and some tools spit warnings about it.
Consider such 'getter' which is perfectly constexpr-able beside the assertion. But I would like to keep some kind of assertion for runtime and compile time, but cannot just overload depending on the 'constexpr' context.
template<int Size>
struct Array {
int m_vals[Size];
constexpr const int& getElement( int idx ) const
{
ASSERT( idx < Size ); // a no-go for constexpr funcs in c++11
// not possible, even in constexpr calls as being pointed out, but what I would like:
static_assert( idx < Size, "out-of-bounds" );
return m_vals[idx];
}
};
Side conditions: C++11, no heap, no exceptions, no compiler specifics.
Note as commenters pointed out (thanks!), static_assert
on the argument is not possible (but would be nice). The compiler gave me a different error on out-of-bounds access in that situation.
Something like
void assert_impl() { assert(false); } // Replace body with own implementation
#ifdef NDEBUG // Replace with own conditional
#define my_assert(condition) ((void)0)
#else
#define my_assert(condition) ((condition) ? (void()) : (assert_impl(), void()))
#endif
template<int Size>
struct Array {
int m_vals[Size];
constexpr const int& getElement( int idx ) const
{
return my_assert(idx < Size), m_vals[idx];
}
};
It will give a compile time error on assertion failure if used in a context requiring a constant expression (because it will call a non-constexpr
function).
Otherwise it will fail at runtime with a call to assert
(or your analogue).
This is the best that you can do as far as I know. There is no way to use the value of idx
to force a check at compile-time outside of context requiring constant expressions.
The comma operator syntax is not nice, but C++11 constexpr
functions are very limited.
Of course, as you already noted, undefined behavior will be diagnosed anyway if the function is used in a context requiring a constant expression.
If you know that assert
(or your analogue) doesn't expand to anything that is forbidden in a constant expression if the condition evaluates to true
but does so if it evaluates to false
, then you can use it directly instead of my_assert
and skip the indirection that I build in my code.
static_assert
cannot be used here. The argument to a constexpr
function is not permitted in a constant-expression. Therefore, there is no solution to your problem under the given constraints.
We can, however, solve the problem by bending two constraints
not using static_assert
(use other methods to produce a compile-time diagnostic instead), and
disregard that the comma operator "is ugly and some tools spit warnings about it." (Showing its ugliness is an unfortunate consequence of the strict requirements of C++11 constexpr
functions)
Then, we can use a normal assert
:
template <int Size>
struct Array {
int m_vals[Size];
constexpr const int& getElement(int idx) const
{
return assert(idx < Size), m_vals[idx];
}
};
In a constant evaluation context, this will emit a compiler error like error: call to non-'constexpr' function 'void __assert_fail(const char*, const char*, unsigned int, const char*)'
.
Better than a comma expression, you can use a ternary conditional. The first operand is your assertion predicate, the second operand is your success expression, and since the third operand can be any expression - even one not usable in a C++11 constant context - you can use a lambda to invoke your library's ASSERT
facility:
#define ASSERT_EXPR(pred, success) \
((pred) ? \
(success) : \
[&]() -> decltype((success)) \
{ \
ASSERT(false && (pred)); \
struct nxg { nxg() {} } nxg; \
return (success); \
}())
Explanation of the body of the lambda:
ASSERT(false && (pred))
is to ensure that your assertion machinery is invoked with an appropriate expression (for stringification).struct nxg { nxg() {} } nxg
is for future-safety, to ensure that if you compile in C++17 or above with NDEBUG
the lambda is still non-constexpr
and so the assertion is enforced in const-evaluation context.return (success)
is there for two reasons: to ensure that the second and third operands have the same type, and so that if your library respects NDEBUG
the success
expression is returned regardless of pred
. (pred
will be evaluated, but you'd hope that assertion predicates should be cheap to evaluate and have no side effects.)Example of use:
template<int Size>
struct Array {
int m_vals[Size];
constexpr int getElement( int idx ) const
{
return ASSERT_EXPR(idx < Size, m_vals[idx]);
}
};
constexpr int I = Array<2>{1, 2}.getElement(1); // OK
constexpr int J = Array<2>{1, 2}.getElement(3); // fails
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