How to dispatch between assert() and static_assert(), dependend if in constexpr context?
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 withNDEBUG
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 respectsNDEBUG
thesuccess
expression is returned regardless ofpred
. (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
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), anddisregard 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*)'
.