14

C++11 では頻繁に、コンテナーをパラメーターとして受け取る関数を定義する必要があります。

たとえば、関数を定義してみましょうaddup(はい、単に の単純なバージョンですstd::accumulate):

template <class I>
int addup (I first, I last)
{
    int x = 0;
    while ( first != last )
        x += *first++;
    return x;
}

これは、柔軟で標準ライブラリのイディオムである反復子の範囲を取ります。

ただし、次の関数があるとします。

vector<T> f();

私はこれをしなければなりません:

auto v = f();
int x = addup(v.begin(), v.end());

私はむしろこれをしたい:

int x = addup(f());

私がこれを行うことができるように:

for (auto t : f())
    ...

範囲ベースの精神で、次のようなものが欲しいです:

template<class C>
int addup(C&& container)
{
    addup(beginexpr(container), endexpr(container)); // ???
}

標準では、6.5.4(言い換え)で次のように述べています。

(A) ifcontainerは配列型でbeginexprendexprarecontainercontainer+boundはそれぞれ、boundはバインドされた配列です。

(B)containerがクラス型である場合、非修飾 IDbeginおよびendは、クラス メンバー アクセス ルックアップ (3.4.5) によるかのように、クラスのスコープ内でcontainerルックアップされ、いずれか (または両方) が少なくとも 1 つの宣言を見つけbeginexprendexprそれぞれ、container.begin() および container.end();

(C) そうでない場合、beginexprおよびendexprはそれぞれbegin(container)およびend(container)であり、begin と end は引数依存のルックアップ (3.4.2) でルックアップされます。

addup4つのケースを処理し、他のオーバーロードと競合しないように、一連のオーバーロードまたは特殊化を定義することは可能ですか? これは、最初に通常のイテレータ ペア関数であり、次に上記の A、B、および C のそれぞれです。どのように?

(これが可能な場合、標準ライブラリがそのようなオーバーロードを提供しないのはなぜですか?)

また、関数がコンテナーを超えて追加のパラメーターを受け取る場合はどうなるでしょうか? オプションの追加パラメーター x(デフォルト値を持つパラメーター) をすべてに追加しても、次の 2 つの呼び出しがあいまいにならないように、オーバーロードを変更できますか。

addup(v.begin(), v.end());
addup(v, x);

つまり、テンプレート パラメーターが反復子、配列、コンテナー クラスなどである必要があることを ("SFINAE" などを使用して) 静的にアサートし、この情報をオーバーロードの明確化に使用できますか?

4

2 に答える 2

11

これは私がすることです:

template<class Range>
int addup(Range&& range)
{
    using std::begin;
    using std::end;
    addup(begin(range), end(range));  // begin(), NOT std::begin()  (ADL)
}

すべての重要なケースを処理し、ADL を適切に実行します。それが ranged-based-for と同等かどうかはわかりませんが、私の意見ではそれが最善の解決策です。

次の 2 つの呼び出しはあいまいです。

私はコンパイルしていませんxが、暗黙の変換が必要でない限り、あいまいさはありません。boost::make_iterator_rangeまた、反復子パラメーターのオーバーロードを使用して回避することもできます。


これもうまくいくと思います:

template<class Range>
int addup(Range&& range)
{
    int x = 0;
    for(auto&& v : range)
        x += v;
    return x; 
}

template <class I>
int addup (I first, I last)
{
    return addup(boost::make_iterator_range(first, last));
}
于 2012-11-25T04:57:01.183 に答える
3

いくつかのケース:

  • 他のパラメータがない場合は、 と を使用してすべてのケースを処理できますstd::beginstd::end
  • タイプがテンプレートではないか、範囲/反復子に依存する (たとえばT::value_type、範囲と反復子の両方で機能する) か、デフォルト値を持つ追加のパラメーターがあります。そしたらまた問題なし。
  • 範囲に関係なく、型がテンプレートであり、デフォルトがない追加のパラメーターがあります。次に、関数を呼び出すときにそのタイプを手動で指定しないと実行できません。

次に例を示します。

template <class Thing, class Iterator>
void DoStuff(Iterator first, Iterator last, Thing thing = Thing())
{ ... }

template <class Thing, class Range>
void DoStuff(Range& range, Thing thing = Thing())
{ ... }


vector<int> coin = {1,2,3,4};
DoStuff(coin, 123); // OK
DoStuff(begin(coin), end(coin), 123); // OK
DoStuff<int>(coin); // OK
DoStuff<int>(begin(coin), end(coin)); // OK

DoStuff(coin); // !! KO
DoStuff(begin(coin), end(coin)); // !! KO

orThingで置き換える(そしてテンプレート引数リストの 2 番目の位置に移動する) と、すべてのオーバーロードが機能します。デフォルト値を指定することもできます。typename Range::value_typeintThing

于 2012-11-25T05:23:48.667 に答える