我之前询问过function overloading based on whether the arguments are constexpr
。我正试图解决这个问题的令人失望的答案,以建立一个更聪明的断言功能。这大致是我想要做的事情:
inline void smart_assert (bool condition) {
if (is_constexpr (condition))
static_assert (condition, "Error!!!");
else
assert (condition);
}
基本上,我们的想法是编译时检查总是比运行时检查更好,如果可以在编译时检查。但是,由于内联和常量折叠之类的东西,我不能总是知道是否可以进行编译时检查。这意味着可能存在assert (condition)
编译为assert(false)
并且代码只是等待我运行它并在我发现错误之前执行该路径的情况。
因此,如果有某种方法来检查条件是否是constexpr(由于内联或其他优化),我可以在可能的情况下调用static_assert
,否则返回运行时断言。幸运的是,gcc具有内在的__builtin_constant_p (exp)
,如果exp
是constexpr,则返回true。我不知道其他编译器是否有这种内在的,但我希望这可以解决我的问题。这是我提出的代码:
#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
static_assert
依赖于or
的短路行为。如果IS_CONSTEXPR
为真,则可以使用static_assert
,条件为!true or condition
,与condition
相同。如果IS_CONSTEXPR
为false,则无法使用static_assert
,条件为!false or condition
,这与true
相同,static_assert
将被忽略。如果由于static_assert
不是constexpr而无法检查condition
,那么我会在代码中添加一个运行时assert
作为最后的努力。但是,由于not being able to use function arguments in a static_assert
,even if the arguments are constexpr
。
特别是,如果我尝试使用gcc编译,会发生这种情况:
// main.cpp
int main () {
smart_assert (false);
return 0;
}
g++ main.cpp -std=c++0x -O0
一切都很好,正常编译。没有内联没有优化,因此IS_CONSTEXPR
为false并忽略static_assert
,因此我只获得运行时assert
语句(失败)。然而,
[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
一旦我打开任何优化并因此可能允许触发static_assert
,它就会失败,因为我无法在static_assert
中使用函数参数。有没有办法解决这个问题(即使它意味着实现我自己的static_assert
)?我觉得我的C ++项目理论上可以从一个更聪明的断言声明中获益,它尽可能早地发现错误。
似乎不会让smart_assert
类似函数的宏在一般情况下解决问题。它显然会在这个简单的例子中起作用,但是condition
可能来自调用图上两级的函数(但由于内联而仍然被编译器称为constexpr
),遇到了在static_assert
中使用函数参数的相同问题。
答案 0 :(得分:8)
这应该可以帮助你开始
template<typename T>
constexpr typename remove_reference<T>::type makeprval(T && t) {
return t;
}
#define isprvalconstexpr(e) noexcept(makeprval(e))
答案 1 :(得分:6)
明确是好的,隐含的是坏的,一般来说。
程序员总是可以尝试static_assert
。
如果在编译时无法评估条件,则失败,程序员需要更改为assert
。
通过提供一个通用表单,您可以更容易地做到这一点,以便更改减少到例如STATIC_ASSERT( x+x == 4 )
→DYNAMIC_ASSERT( x+x == 4 )
,只需重命名。
那就是说,因为在你的情况下你只想优化程序员的时间如果可以进行优化,即因为你可能不关心所有编译器总是得到相同的结果,你可以尝试像...这样的东西。
#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 );
}
如果你这样做,那么请告诉我们它是如何淘汰的。
注意:上面的代码确实在MSVC 10.0和g ++ 4.6中产生了不同的结果。
更新:我想知道关于上面的代码是如何工作的评论,得到了如此多的赞成。我想也许他说的是我根本不懂的东西。所以我开始做OP的工作,检查这个想法的表现。
此时我认为如果 constexpr
函数可以使用g ++,那么也可以为g ++解决问题,否则只能用于其他编译器。
以上是我对g ++支持的支持。使用我提出的想法,这适用于Visual C ++,可以很好地解决(解决OP的问题)。但不是用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
}
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] > _
也就是说,g ++报告(甚至通过它的内在函数,甚至是创建VLA的wrt报告)它知道其值的非常量变量是不变的,但是它不能应用这些知识整数除法,以便它不会产生警告。
哎呀。
更新2 :嗯,我很愚蠢:当然,在任何情况下,宏都可以添加一个普通的assert
。由于OP只对静态断言(如果可用)感兴趣,因此在某些极端情况下不适用于g ++。问题解决了,最初解决了。
答案 2 :(得分:0)
另一个问题的答案如何令人失望?除了编译器在诊断消息中打印文本的方式外,它几乎完全实现了您目前描述的内容。
需要使用throw
完成的原因是,在运行时可以评估的上下文中constexpr
的编译时评估是可选的。例如,实现可以选择让您在调试模式下单步执行constexpr
代码。
constexpr
是函数(声明说明符)的弱属性,无法使用该函数更改表达式的结果值。它保证运行时的语义在编译时是固定的,但不允许您指定特殊的编译时快捷方式。
对于标记无效条件,throw
是一个子表达式,作为常量表达式无效,除非隐藏在?:
,&&
或{{1}的未评估方面}。该语言保证在编译时将标记它,即使调试器允许您在运行时单步调试它,并且只有在标志真正触发时才会标记。
语言在这里得到了正确的答案。遗憾的是,无法与||
的特殊诊断消息功能或static_assert
上的分支进行协调。