constexpr, static_assert, and inlining

constexpr, static_assert, and inlining

本文关键字:and inlining assert static constexpr      更新时间:2023-10-16

我之前根据参数是否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。如果由于condition不是 constexpr 而无法检查static_assert,那么我将在代码中添加运行时assert作为最后的努力。但是,这不起作用,因为无法在static_assert中使用函数参数,即使参数constexpr

特别是,如果我尝试使用 gcc 编译,就会发生这种情况:

// main.cpp
int main () {
    smart_assert (false);
    return 0;
}

g++ main.cpp -std=c++0x -O0

一切都很好,编译正常。没有没有优化的内联,所以IS_CONSTEXPR是假的,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中使用函数参数的相同问题。

这应该可以帮助您开始

template<typename T> 
constexpr typename remove_reference<T>::type makeprval(T && t) {
  return t;
}
#define isprvalconstexpr(e) noexcept(makeprval(e))

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

程序员总是可以尝试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++ 支持所获得的。这很好地适用于视觉C++,使用我提出的想法。但不是使用 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)它知道其值的非常量变量是常量,但是它未能将该知识应用于整数除法,因此它无法产生警告。

唉。


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

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

需要使用throw完成此操作的原因是,在运行时可以评估的上下文中对constexpr进行编译时评估是可选的。例如,实现可以选择让你在调试模式下单步执行constexpr代码。

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

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

语言在这里得到了正确的事情。不幸的是,这与static_assert或分支constexpr的特殊诊断消息功能无法调和。