web-dev-qa-db-ja.com

constexpr関数内のコンパイル時または実行時の検出

ConstexprがC++ 11で導入されたとき、私は興奮しましたが、残念ながら、その有用性について楽観的な仮定をしました。 constexprをどこでも使用して、リテラルのコンパイル時定数、またはリテラルのコンパイル時定数のconstexprの結果(次のようなものを含む)をキャッチできると仮定しました。

constexpr float MyMin(constexpr float a, constexpr float b) { return a<b?a:b; }

関数の戻り型をconstexprとしてのみ修飾することは、その使用をコンパイル時に制限せず、実行時にも呼び出すことができる必要があるため、これは、MyMinがコンパイル時に評価される定数でのみ使用できるようにする方法であると考えました。 、これにより、コンパイラが実行時に実行を許可しないことが保証され、MyMinのよりランタイムに適した代替バージョンを作成できるようになります。理想的には、_mm_min_ss組み込み関数を使用する同じ名前で、コンパイラがランタイム分岐を生成しないようにします。コード。残念ながら、関数パラメーターをconstexprにすることはできないため、次のようなことが可能でない限り、これを行うことはできないように思われます。

constexpr float MyMin(float a, float b)
{
#if __IS_COMPILE_TIME__
    return a<b?a:b;
#else
    return _mm_cvtss_f32(_mm_min_ss(_mm_set_ss(a),_mm_set_ss(b)));
#endif
}

MSVC++にこのようなものがあるかどうかは深刻な疑問ですが、GCCまたはclangに少なくともそれを達成するための何かがあることを望んでいましたが、それがいかに不法に見えるかもしれません。

確かに、私が提示した例は非常に単純ですが、想像力を働かせれば、コンパイル時にしか実行できないことがわかっている関数内で分岐ステートメントを多用するなど、自由にできる場合が多くあります。実行時に実行すると、パフォーマンスが低下するためです。

20
Kumputer

特定の関数呼び出し式が定数式であるかどうかを検出し、それによって2つの異なる実装から選択することができます。以下で使用する汎用ラムダにはC++ 14が必要です。

(この回答は成長しました @ Yakkからのこの回答 昨年私が尋ねた質問へ)。

どこまでスタンダードを押し上げているのかわかりません。これはclang3.9でテストされていますが、g ++ 6.2で「内部コンパイラエラー」が発生します。来週バグレポートを送信します(他に誰も最初に送信しない場合)。

この最初のステップは、constexpr実装を_constexpr static_メソッドとしてstructに移動することです。もっと簡単に言えば、現在のconstexprをそのままにして、新しいstructの_constexpr static_メソッドから呼び出すことができます。

_struct StaticStruct {
    static constexpr float MyMin_constexpr (float a, float b) {
        return a<b?a:b;
    }
};
_

また、これを定義します(役に立たないように見えますが!):

_template<int>
using Void = void;
_

基本的な考え方は、_Void<i>_ではiが定数式である必要があるということです。より正確には、この次のラムダは、特定の状況でのみ適切なオーバーロードを持ちます。

_auto l = [](auto ty)-> Void<(decltype(ty)::   MyMin_constexpr(1,3)   ,0)>{};
                                              \------------------/
                                               testing if this
                                               expression is a
                                               constant expression.
_

引数lの型がtyandである場合にのみ、StaticStructを呼び出すことができます(MyMin_constexpr(1,3))定数式です。 _1_または_3_を非定数引数に置き換えると、ジェネリックラムダlはSFINAEを介してメソッドを失います。

したがって、次の2つのテストは同等です。

  • StaticStruct::MyMin_constexpr(1,3) a 定数式ですか?
  • ll(StaticStruct{})を介して呼び出すことができますか?

上記のラムダから_auto ty_とdecltype(ty)を単純に削除したくなります。ただし、これにより、Nice置換の失敗ではなく、ハードエラー(一定でない場合)が発生します。したがって、エラーの代わりに_auto ty_を使用して、置換の失敗(便利に検出できます)を取得します。

この次のコードは、f(ジェネリックラムダ)をaStaticStruct)で呼び出すことができる場合にのみ、_std:true_type_を返す簡単なものです。

_template<typename F,typename A>
constexpr
auto
is_a_constant_expression(F&& f, A&& a)
    -> decltype( ( std::forward<F>(f)(std::forward<A>(a)) , std::true_type{} ) )
{ return {}; }
constexpr
std::false_type is_a_constant_expression(...)
{ return {}; }
_

次に、その使用法のデモンストレーション:

_int main() {
    {
        auto should_be_true = is_a_constant_expression(
            [](auto ty)-> Void<(decltype(ty)::   MyMin_constexpr(1,3)   ,0)>{}
            , StaticStruct{});
        static_assert( should_be_true ,"");
    }
    {   
        float f = 3; // non-constexpr
        auto should_be_false = is_a_constant_expression(
            [](auto ty)-> Void<(decltype(ty)::   MyMin_constexpr(1,f)   ,0)>{}
            , StaticStruct{});
        static_assert(!should_be_false ,"");
    }
}
_

元の問題を直接解決するために、最初にマクロを定義して繰り返しを保存することができます。

(私はこのマクロをテストしていません。タイプミスについてお詫びします。)

_#define IS_A_CONSTANT_EXPRESSION( EXPR )                \
     is_a_constant_expression(                          \
         [](auto ty)-> Void<(decltype(ty)::             \
              EXPR                         ,0)>{}       \
         , StaticStruct{})
_

この段階で、おそらくあなたは簡単に行うことができます:

_#define MY_MIN(...)                                            \
    IS_A_CONSTANT_EXPRESSION( MyMin_constexpr(__VA_ARGS__) ) ? \
        StaticStruct :: MyMin_constexpr( __VA_ARGS__ )     :   \
                        MyMin_runtime  ( __VA_ARGS__ )
_

または、コンパイラが_std::true_type_および_std::false_type_から_?:_を最適化することを信頼していない場合は、おそらく次のようになります。

_constexpr
float MyMin(std::true_type, float a, float b) { // called if it is a constant expression
    return StaticStruct:: MyMin_constexpr(a,b);
}
float MyMin(std::false_type, float , float ) { // called if NOT a constant expression
    return                MyMin_runtime(a,b);
}
_

代わりにこのマクロを使用して:

_#define MY_MIN(...)                                             \
  MyMin( IS_A_CONSTANT_EXPRESSION(MyMin_constexpr(__VA_ARGS__)) \
       , __VA_ARGS__)
_
16
Aaron McDaid

これは、MyMinがコンパイル時に評価される定数でのみ使用できるようにする方法であり、コンパイラーが実行時に実行を許可しないようにする方法であると考えました。

はい;やり方がある。

また、C++ 11でも動作します。

Google-ing私が見つけた 奇妙な中毒の方法 (Scott Schurrによる):要するに、次の

_extern int no_symbol;

constexpr float MyMin (float a, float b)
 {
   return a != a ? throw (no_symbol)
                 : (a < b ? a : b) ;
 }

int main()
 {
   constexpr  float  m0 { MyMin(2.0f, 3.0f) }; // OK

   float  f1 { 2.0f };

   float  m1 { MyMin(f1, 3.0f) };  // linker error: undefined "no_symbol"
 }
_

私がよく理解している場合、その背後にある考え方は、MyMin()がコンパイル時に実行される場合、throw(no_symbol)は使用されない(_a != a_は常にfalse)ため、使用する必要がないということです。 externと宣言されているが定義されていない_no_symbol_を使用します(また、throw()はコンパイル時に使用できません)。

MyMin()ランタイムを使用する場合、throw(no_symbol)がコンパイルされ、_no_symbol_はリンクフェーズでエラーを出します。

より一般的に言えば、 提案 (Scott Schurrから)がありますが、私は実装を認識していません。

---編集---

T.C.が指摘したように(ありがとう!)このソリューションが機能するのは、コンパイラが_a != a_が常にfalseであることを理解するために、ある時点で最適化されていないためです。

特に、MyMin()は(適切な最適化なしで)機能します。これは、この例では浮動小数点数を使用しており、aがNaNの場合は_a != a_がtrueになる可能性があるためです。コンパイラがthrow()部分が役に立たないことを検出するのはさらに困難です。 MyMin()が整数の関数である場合、本体は次のように記述できます(complilerの最適化を妨害しようとするテストfloat(a) != float(a)を使用)

_constexpr int MyMin (int a, int b)
 {
   return float(a) != float(a) ? throw (no_symbol)
                 : (a < b ? a : b) ;
 }
_

しかし、「自然な」スロー可能なエラーケースがない関数の実際の解決策ではありません。

エラー(コンパイルまたは実行)が発生するはずの自然なエラーの場合は異なります。コンパイラーは最適化できず、トリックは機能します。

例:MyMin()abの間の最小値を返すが、abが異なる場合またはMyMin()はコンパイラエラーを与えるはずです(良い例ではありません...私は知っています)ので、

_constexpr float MyMin (float a, float b)
 {
   return a != b ? throw (no_symbol)
                 : (a < b ? a : b) ;
 }
_

コンパイラーは_a != b_を最適化できず、throw()部分をコンパイル(リンカーエラーを与える)する必要があるため、機能します。

7
max66