1

次の単純な関数を考えてみましょう

void foo_rt(int n) {
    for(int i=0; i<n; ++i) {
        // ... do something relatively cheap ...
    }
}

コンパイル時にパラメーターがわかっている場合nは、同じ関数のテンプレートバージョンを作成できます。

template<int n>
void foo_ct() {
    for(int i=0; i<n; ++i) {
        // ... do something relatively cheap ...
    }
}

これにより、コンパイラはループ展開などを実行できるようになり、速度が向上します。

しかし、ここで、コンパイル時実行時のみを知っている仮定します。関数の2つのバージョンを維持せずにこれを実装するにはどうすればよいですか?私は線に沿って何かを考えていました:n

inline void foo(int n) {
    for(int i=0; i<n; ++i) {
        // ... do something relatively cheap ...
    }
}

// Runtime version
void foo_rt(int n) { foo(n); }

// Compiletime version
template<int n>
void foo_ct() { foo(n); }

しかし、すべてのコンパイラがこれに対処するのに十分賢いかどうかはわかりません。もっと良い方法はありますか?

編集:

明らかに、機能する1つの解決策はマクロを使用することですが、これは本当に避けたいものです。

#define foo_body \
{ \
    for(int i=0; i<n; ++i) { \
        // ... do something relatively cheap ... \
    } \
}

// Runtime version
void foo_rt(int n) foo_body

// Compiletime version
template<int n>
void foo_ct() foo_body
4

5 に答える 5

4

integral_variableタイプとを使用して、以前にこれを行いましたstd::integral_constant。これは多くのコードのように見えますが、もう一度見てみると、実際には一連の 4 つの非常に単純な部分にすぎず、そのうちの 1 つは単なるデモ コードです。

#include <type_traits>

//type for acting like integeral_constant but with a variable
template<class underlying>
struct integral_variable {
    const underlying value;
    integral_variable(underlying v) :value(v) {}
}; 

//generic function
template<class value> 
void foo(value n) {
    for(int i=0; i<n.value; ++i) {
        // ... do something relatively cheap ...
    }
} 

//optional: specialize so callers don't have to do casts
void foo_rt(int n) { return foo(integral_variable<int>(n)); }
template<int n>
void foo_ct() { return foo(std::integral_constant<unsigned, n>()); }
//notice it even handles different underlying types.  Doesn't care.

//usage is simple
int main() {
    foo_rt(3);
    foo_ct<17>();
}
于 2012-06-08T20:02:18.427 に答える
1

私はDRY 原則を高く評価していますが、それを 2 回書く方法はないと思います。

コードは同じですが、これらは 2 つの非常に異なる操作です。つまり、既知の値を操作する場合と未知の値を操作する場合です。

既知のものを最適化への近道に乗せたいと考えていますが、未知のものは適さない可能性があります。

私がすることは、 n に依存しないすべてのコードを別の関数 (できればforループの本体全体) に分解し、テンプレート化されたバージョンとテンプレート化されていないバージョンの両方がループ内でそれを呼び出すようにすることです。そうすれば、繰り返しているのはループの構造だけです。forこれは大したことではありません。

于 2012-06-08T19:55:42.033 に答える
0

値がコンパイル時に既知の場合、テンプレート パラメーターとしてテンプレートを介してルーティングしても、コンパイル時にそれ以上認識されることはありません。変数が他の種類のコンパイル時定数ではなくテンプレート パラメーターであるという理由だけで、関数をインライン化して最適化するコンパイラーが存在する可能性はほとんどないと思います。

コンパイラによっては、2 つのバージョンの関数が必要ない場合もあります。最適化コンパイラは、定数式パラメータで呼び出された関数を最適化できる可能性があります。例えば:

extern volatile int *I;

void foo(int n) {
    for (int i=0;i<n;++i)
        *I = i;
}

int main(int argc,char *[]) {
    foo(4);
    foo(argc);
}

私のコンパイラは、これを 0 から 3 までのインライン展開されたループに変換し、続いて argc のインライン ループを作成します。

main:                                   # @main
# BB#0:                                 # %entry
        movq    I(%rip), %rax
        movl    $0, (%rax)
        movl    $1, (%rax)
        movl    $2, (%rax)
        movl    $3, (%rax)
        testl   %ecx, %ecx
        jle     .LBB1_3
# BB#1:                                 # %for.body.lr.ph.i
        xorl    %eax, %eax
        movq    I(%rip), %rdx
        .align  16, 0x90
.LBB1_2:                                # %for.body.i4
                                        # =>This Inner Loop Header: Depth=1
        movl    %eax, (%rdx)
        incl    %eax
        cmpl    %eax, %ecx
        jne     .LBB1_2
.LBB1_3:                                # %_Z3fooi.exit5
        xorl    %eax, %eax
        ret

このような最適化を行うには、定義がすべての翻訳単位で利用できるようにする (たとえば、ヘッダー ファイルで関数をインラインとして定義する) か、リンク時の最適化を行うコンパイラを用意する必要があります。

これを使用していて、コンパイル時に計算されるものに本当に依存している場合は、それが完了したことを確認する自動テストが必要です。


C++11 は constexpr を提供します。これにより、constexpr パラメーターが指定されたときにコンパイル時に計算される関数を記述したり、コンパイル時に値が計算されることを保証したりできます。constexpr 関数に入れることができるものには制限があるため、関数を constexpr として実装するのが難しくなる可能性がありますが、許可されている言語は完全にチューリングされているようです。1 つの問題は、constexpr パラメーターが与えられた場合、制限によりコンパイル時に計算を実行できることが保証されますが、これらの制限により、パラメーターがconstexprでない場合に非効率的な実装になる可能性があることです。

于 2012-06-08T20:53:45.307 に答える
-1

あなたが持っている場合、私はそれを指摘します:

// Runtime version
void foo_rt(int n){ foo(n);}

...そしてこれはあなたのために働きます、そしてあなた実際にコンパイル時に型を知っています。少なくとも、それが共変である型を知っており、それだけで十分です。テンプレート化されたバージョンをそのまま使用できます。必要に応じて、次のように呼び出しサイトで型を指定できます。

foo_rt<int>()
于 2012-06-08T18:58:07.237 に答える