7

1 つのステートメントを使用する場合と比較して、取得するメソッド/関数true_typeまたはパラメーターをオーバーロードする利点はありますか? パラメータとオーバーロードされたメソッドを使用するコードがますます増えています。false_typeiftrue_typefalse_type

ifステートメントを使用した短い例

void coutResult(bool match)
{
    if (match)
        cout << "Success." << endl;
    else
        cout << "Failure." << endl;
}

bool match = my_list.contains(my_value);
coutResult(match);

オーバーロードされた関数を使用する場合と比較して:

void coutResult(true_type)
{
    cout << "Success." << endl;
}

void coutResult(false_type)
{
    cout << "Failure." << endl;
}

bool match = my_list.contains(my_value);
coutResult(match);
4

3 に答える 3

13

2 番目のサンプル コードはコンパイルされません。これは、コンパイル時のオーバーロード解決と、実行するコードを「選択」するための実行時の条件分岐の違いの症状です。

  • 「取る関数true_typeまたはパラメーターをオーバーロードする」により、決定が型とコンパイル時の定数のみに依存する場合、コンパイル時false_typeにこの選択を行うことができます。
  • いくつかの変数値がわかっている実行時ifまで選択を行うことができない場合は、「チェックの使用」が必要です。

あなたの例でbool match = my_list.contains(my_value)は、プログラムを実行する前に明らかにわからないため、オーバーロードを使用できません。

しかし違いはテンプレートにとって最も重要であり、選択は「実行するパス」だけでなく、「インスタンス化してコンパイルしてから呼び出すコード」です。リンクされたビデオのコードは、むしろこの精神に基づいています。

この(間違った)コードを考えてみましょう(簡潔にするために#includes とstd::s を省略しています):

template<typename InIt>
typename iterator_traits<InIt>::difference_type
distance(InIt first, InIt last)
{
    // Make code shorter
    typedef typename iterator_traits<InIt>::difference_type Diff;
    typedef typename iterator_traits<InIt>::iterator_category Tag;

    // Choice
    if (is_same<Tag, random_access_iterator_tag>::value)
    {
        return last - first;
    }
    else
    {
        Diff n = 0;
        while (first != last) {
            ++first;
            ++n;
        }
        return n;
    }
}

ここには少なくとも 2 つの問題があります。

  • ランダム アクセスではないイテレータ (例: ) で呼び出そうとするとstd::list<T>::iterator、実際にはコンパイルに失敗します(行を指すエラーが発生しますreturn last - first;)。コンパイラは、分岐と分岐の両方を含む関数本体全体をインスタンス化してコンパイルする必要があり (実行されるのは 1 つだけですが)、式は RA 以外の反復子に対しては無効です。ifelselast - first
  • それがコンパイルされたとしても、コンパイル時にすぐにテストできた条件について実行時に(関連するオーバーヘッドを使用して)テストを実行し、不要なコード部分をコンパイルすることになります。(コンパイラはそれを最適化できるかもしれませんが、それがコンセプトです。)

それを修正するには、次のことができます。

// (assume needed declarations...)

template<typename InIt>
typename iterator_traits<InIt>::difference_type
distance(InIt first, InIt last)
{
    // Make code shorter
    typedef typename iterator_traits<InIt>::iterator_category Tag;

    // Choice
    return distanceImpl(first, last, is_same<Tag, random_access_iterator_tag>());
}

template<typename InIt>
typename iterator_traits<InIt>::difference_type
distanceImpl(InIt first, InIt last, true_type)
{
    return last - first;
}

template<typename InIt>
typename iterator_traits<InIt>::difference_type
distanceImpl(InIt first, InIt last, false_type)
{
    // Make code shorter
    typedef typename iterator_traits<InIt>::difference_type Diff;

    Diff n = 0;
    while (first != last) {
        ++first;
        ++n;
    }
    return n;
}

または代わりに (ここで可能) 型を直接:

/* snip */
distance(InIt first, InIt last)
{
    /* snip */
    return distanceImpl(first, last, Tag());
}

/* snip */
distanceImpl(InIt first, InIt last, random_access_iterator_tag)
{
    return last - first;
}

/* snip */
distanceImpl(InIt first, InIt last, input_iterator_tag)
{
    /* snip */
    Diff n = 0;
    /* snip */
    return n;
}

これで、「正しい」ものだけdistanceImplがインスタンス化されて呼び出されます (選択はコンパイル時に行われます)。

これが機能するのは、型 (InItまたは などTag) がコンパイル時にis_same<Tag, random_access_iterator_tag>::value既知であり、コンパイル時にも既知の定数であるためです。コンパイラは、型に基づいてのみ、呼び出されるオーバーロードを解決できます (オーバーロードの解決)。

注: 「タグ」は値で渡されますが、「ディスパッチ」の名前のない未使用のパラメーターとしてのみ使用され (値は使用されず、型のみが使用されます)、コンパイラーはそれらを最適化できます。

また、Scott Meyers の「Effective C++, Third Edition 」から、Item 47: Use traits classes を読んで型に関する情報を得ることができます。

于 2013-06-30T10:48:42.527 に答える