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であり、これは単に と同じconditionです。IS_CONSTEXPRが false の場合はstatic_assert使用できず、条件は!false or conditionであり、これは と同じでtrueあり、static_assertは無視されます。static_assertが constexpr ではないためにチェックできない場合は、最後の努力としてconditionランタイムをコードに追加します。assertただし、引数が であっても、関数の引数を astatic_assertで使用できないため、これは機能しません。constexpr

特に、gcc でコンパイルしようとすると、次のようになります。

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

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

すべて問題なく、正常にコンパイルされます。最適化なしのインライン化はないため、IS_CONSTEXPRfalse であり、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++ プロジェクトは、できるだけ早くエラーをキャッチするよりスマートな assert ステートメントから理論的にかなりの恩恵を受けることができると思います。

smart_assert関数のようなマクロを作成しても、一般的なケースでは問題が解決しないようです。この単純な例では明らかにcondition機能しますが、コール グラフの 2 レベル上の関数から発生した可能性があります (ただし、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 の作業を開始し、アイデアがどのように実現したかを確認しました。

この時点で、関数を g++ で動作させることができればconstexpr、 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隠されている場合を除いて、定数式として無効な部分式です。この言語は、デバッガーが実行時にステップスルーできる場合でも、フラグが実際にトリガーされた場合にのみ、コンパイル時にフラグが立てられることを保証します。?:&&||

言語はここで物事を正しく理解します。残念ながら、それは-nessの特別な診断メッセージ機能static_assertまたは分岐と調整することはできません。constexpr

于 2012-04-23T09:13:12.293 に答える