问题
I previously asked about function overloading based on whether the arguments are constexpr. I'm trying to work around the disappointing answer to that question to make a smarter assert function. This is roughly what I am trying to do:
inline void smart_assert (bool condition) {
if (is_constexpr (condition))
static_assert (condition, "Error!!!");
else
assert (condition);
}
Basically, the idea is that a compile-time check is always better than a run-time check if it's possible to check at compile time. However, due to things like inlining and constant folding, I can't always know whether a compile time check is possible. This means that there may be cases where assert (condition)
compiles down to assert(false)
and the code is just waiting for me to run it and execute that path before the I find out there is an error.
Therefore, if there were some way to check whether the condition is a constexpr (due to inlining or other optimizations), I could call static_assert
when possible, and fall back on a run-time assert otherwise. Fortunately, gcc has the intrinsic __builtin_constant_p (exp)
, which returns true if exp
is a constexpr. I don't know if other compilers have this intrinsic, but I was hoping that this would solve my problem. This is the code that I came up with:
#include <cassert>
#undef IS_CONSTEXPR
#if defined __GNUC__
#define IS_CONSTEXPR(exp) __builtin_constant_p (exp)
#else
#define IS_CONSTEXPR(exp) false
#endif
// TODO: Add other compilers
inline void smart_assert (bool const condition) {
static_assert (!IS_CONSTEXPR(condition) or condition, "Error!!!");
if (!IS_CONSTEXPR(condition))
assert (condition);
}
#undef IS_CONSTEXPR
The static_assert
relies on the short circuit behavior of or
. If IS_CONSTEXPR
is true, then static_assert
can be used, and the condition is !true or condition
, which is the same as just condition
. If IS_CONSTEXPR
is false, then static_assert
cannot be used, and the condition is !false or condition
, which is just the same as true
and the static_assert
is ignored. If the static_assert
cannot be checked because condition
is not a constexpr, then I add a run-time assert
to my code as a last-ditch effort. However, this does not work, thanks to not being able to use function arguments in a static_assert, even if the arguments are constexpr.
In particular, this is what happens if I try to compile with gcc:
// main.cpp
int main () {
smart_assert (false);
return 0;
}
g++ main.cpp -std=c++0x -O0
Everything is fine, compiles normally. There is no inlining with no optimization, so IS_CONSTEXPR
is false and the static_assert
is ignored, so I just get a run-time assert
statement (that fails). However,
[david@david-desktop test]$ g++ main.cpp -std=c++0x -O1
In file included from main.cpp:1:0:
smart_assert.hpp: In function ‘void smart_assert(bool)’:
smart_assert.hpp:12:3: error: non-constant condition for static assertion
smart_assert.hpp:12:3: error: ‘condition’ is not a constant expression
As soon as I turn on any optimizations and thus potentially allow static_assert
to be triggered, it fails because I cannot use function arguments in the static_assert
. Is there some way to work around this (even if it means implementing my own static_assert
)? I feel my C++ projects could theoretically benefit quite a bit from a smarter assert statement that catches errors as early as possible.
It doesn't seem like making smart_assert
a function-like macro will solve the problem in the general case. It will obviously make it work in this simple example, but condition
may have come from a function two levels up the call graph (but still becomes known to the compiler as a constexpr
due to inlining), which runs into the same problem of using a function parameter in a static_assert
.
回答1:
This should help you start
template<typename T>
constexpr typename remove_reference<T>::type makeprval(T && t) {
return t;
}
#define isprvalconstexpr(e) noexcept(makeprval(e))
回答2:
Explicit is good, implicit is bad, in general.
The programmer can always try a static_assert
.
If the condition can not be evaluated at compile time, then that fails, and the programmer needs to change to assert
.
You can make it easier to do that by providing a common form so that the change reduces to e.g. STATIC_ASSERT( x+x == 4 )
→ DYNAMIC_ASSERT( x+x == 4 )
, just a renaming.
That said, since in your case you only want an optimization of the programmer’s time if that optimization is available, i.e. since you presumably don’t care about getting the same results always with all compilers, you could always try something like …
#include <iostream>
using namespace std;
void foo( void const* ) { cout << "compile time constant" << endl; }
void foo( ... ) { cout << "hm, run time,,," << endl; }
#define CHECK( e ) cout << #e << " is "; foo( long((e)-(e)) )
int main()
{
int x = 2134;
int const y = 2134;
CHECK( x );
CHECK( y );
}
If you do, then please let us know how it panned out.
Note: the above code does produce different results with MSVC 10.0 and g++ 4.6.
Update: I wondered how the comment about how the code above works, got so many upvotes. I thought maybe he's saying something I simply don't understand. So I set down to do the OP's work, checking how the idea fared.
At this point I think that if the constexpr
function thing can be be made to work with g++, then it's possible to solve the problem also for g++, otherwise, only for other compilers.
The above is as far as I got with g++ support. This works nicely (solves the OP's problem) for Visual C++, using the idea I presented. But not with g++:
#include <assert.h>
#include <iostream>
using namespace std;
#ifdef __GNUC__
namespace detail {
typedef double (&Yes)[1];
typedef double (&No)[2];
template< unsigned n >
Yes foo( char const (&)[n] );
No foo( ... );
} // namespace detail
#define CASSERT( e ) \
do { \
char a[1 + ((e)-(e))]; \
enum { isConstExpr = sizeof( detail::foo( a ) ) == sizeof( detail::Yes ) }; \
cout << "isConstExpr = " << boolalpha << !!isConstExpr << endl; \
(void)(isConstExpr? 1/!!(e) : (assert( e ), 0)); \
} while( false )
#else
namespace detail {
struct IsConstExpr
{
typedef double (&YesType)[1];
typedef double (&NoType)[2];
static YesType check( void const* );
static NoType check( ... );
};
} // namespace detail
#define CASSERT( e ) \
do { \
enum { isConstExpr = \
(sizeof( detail::IsConstExpr::check( e - e ) ) == \
sizeof( detail::IsConstExpr::YesType )) }; \
(void)(isConstExpr? 1/!!(e) : (assert( e ), 0)); \
} while( false )
#endif
int main()
{
#if defined( STATIC_TRUE )
enum { x = true };
CASSERT( x );
cout << "This should be displayed, OK." << endl;
#elif defined( STATIC_FALSE )
enum { x = false };
CASSERT( x );
cerr << "!This should not even have compiled." << endl;
#elif defined( DYNAMIC_TRUE )
bool x = true;
CASSERT( x );
cout << "This should be displayed, OK." << endl;
#elif defined( DYNAMIC_FALSE )
bool x = false;
CASSERT( x );
cout << "!Should already have asserted." << endl;
#else
#error "Hey, u must define a test case symbol."
#endif
}
Example of the problem with g++:
[D:\dev\test] > g++ foo.cpp -Werror=div-by-zero -D DYNAMIC_FALSE [D:\dev\test] > a isConstExpr = true !Should already have asserted. [D:\dev\test] > _
That is, g++ reports (even via its intrinsic function, and even wrt. creating VLA or not) that a non- const` variable that it knows the value of, is constant, but then it fails to apply that knowledge for integer division, so that it then fails to produce warning.
Argh.
Update 2: Well I'm dumb: of course the macro can just add an ordinary assert
to have there in any case. Since the OP is only interested in getting the static assert when it's available, which isn't for g++ in some corner cases. Problem solved, and was solved originally.
回答3:
How is the answer to the other question disappointing? It implements almost exactly what you presently describe, except for the way the compiler prints the text in the diagnostic message.
The reason it needs to be done with throw
is that compile-time evaluation of constexpr
in a context that could be evaluated at runtime is optional. For example, the implementation could choose to let you step through the constexpr
code in debugging mode.
constexpr
is a weak attribute of functions (declaration specifier) that cannot change the resulting value of an expression using the function. It is a guarantee that the semantic meaning at runtime is fixed at compile time, but doesn't allow you to specify a special compile-time shortcut.
As for flagging invalid conditions, throw
is a subexpression which is invalid as a constant expression, except when hidden in the unevaluated side of ?:
, &&
, or ||
. The language guarantees that this will be flagged at compile time, even if the debugger lets you step through it at runtime, and only if the flag is really triggered.
The language gets things right here. Unfortunately that cannot be reconciled with the special diagnostic message feature of static_assert
or branching on constexpr
-ness.
来源:https://stackoverflow.com/questions/10275300/constexpr-static-assert-and-inlining