constexpr,static_assert和inlining

时间:2012-04-23 05:35:54

标签: c++ c++11 inline constexpr static-assert

我之前询问过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_asserteven 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中使用函数参数的相同问题。

3 个答案:

答案 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上的分支进行协调。