14

我之前根据参数是否为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 有内在函数,如果是 constexpr __builtin_constant_p (exp),它会返回 true 。exp我不知道其他编译器是否有这个内在,但我希望这能解决我的问题。这是我想出的代码:

#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,与just 相同condition。如果IS_CONSTEXPR为假,则static_assert不能使用,条件为!false or condition,与 相同,true忽略static_assert。如果static_assert由于不是 constexpr 而无法检查condition,那么我会在我的代码中添加一个运行时assert作为最后的努力。但是,这不起作用,因为不能在 a 中使用函数参数static_assert即使参数是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

4

3 回答 3

9

这应该可以帮助您开始

template<typename T> 
constexpr typename remove_reference<T>::type makeprval(T && t) {
  return t;
}

#define isprvalconstexpr(e) noexcept(makeprval(e))
于 2012-04-23T20:15:09.893 回答
7

一般来说,显式是好的,隐式是坏的。

程序员总是可以尝试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:\开发\测试]
> g++ foo.cpp -Werror=div-by-zero -D DYNAMIC_FALSE

[D:\开发\测试]
> 一个
isConstExpr = true
!应该已经断言了。

[D:\开发\测试]
> _

也就是说,g++ 报告(甚至通过其内在函数,甚至是否创建 VLA)它知道其值的非 const` 变量是常量,但是它无法将该知识应用于整数除法,这样它就无法产生警告。

啊。


更新2:好吧,我很愚蠢:当然,宏可以assert在任何情况下添加一个普通的。由于 OP 只对获取可用的静态断言感兴趣,这在某些极端情况下不适用于 g++。问题解决了,本来就解决了。

于 2012-04-23T06:26:54.820 回答
0

另一个问题的答案如何令人失望?它几乎完全实现了您目前描述的内容,除了编译器在诊断消息中打印文本的方式。

需要这样做的原因是在可以在运行时评估的上下文中throw的编译时评估constexpr是可选的。例如,实现可以选择让您constexpr在调试模式下单步执行代码。

constexpr是函数的弱属性(声明说明符),它不能使用函数更改表达式的结果值。它保证运行时的语义在编译时是固定的,但不允许您指定特殊的编译时快捷方式。

至于标记无效条件,throw是作为常量表达式无效的子表达式,除非隐藏在 、 或 的未?:计算&&||。该语言保证这将在编译时被标记,即使调试器允许您在运行时单步执行它,并且只有当标记被真正触发时。

语言在这里得到了东西。static_assert不幸的是,这与 -ness 的特殊诊断消息功能或分支无法协调constexpr

于 2012-04-23T09:13:12.293 回答