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 sm
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.
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.
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))