16

呼び出すための確立されたイディオムswapは次のとおりです。

using std::swap
swap(foo, bar);

このようにして、名前空間swap外のユーザー定義型に対してオーバーロードできます。std

同じようにbeginandを呼び出す必要がありますか?end

using std::begin;
using std::end;
some_algorithm(begin(some_container), end(some_container));

または、次のように書く必要があります。

some_algorithm(std::begin(some_container), std::end(some_container));
4

4 に答える 4

9

using-そのような宣言を使用することは、IMOの正しい方法です。これは、標準が for ループの範囲で行うことでもあります。beginまたはメンバーが存在しない場合、関連付けられた名前空間としてandをend呼び出します (つまり、ADL が非メンバーのand を検出しない場合はandを検出します)。begin(x)end(x)stdstd::beginstd::endbeginend

常に書くusing std::begin; using std::end;のが面倒だと思う場合は、以下の関数adl_beginと関数を使用できます。adl_end

namespace aux {

using std::begin;
using std::end;

template<class T>
auto adl_begin(T&& x) -> decltype(begin(std::forward<T>(x)));

template<class T>
auto adl_end(T&& x) -> decltype(end(std::forward<T>(x)));

template<class T>
constexpr bool is_array()
{
    using type = typename std::remove_reference<T>::type;
    return std::is_array<type>::value;
}

} // namespace aux

template<class T,
         class = typename std::enable_if<!aux::is_array<T>()>::type>
auto adl_begin(T&& x) -> decltype(aux::adl_begin(std::forward<T>(x)))
{
    using std::begin;
    return begin(std::forward<T>(x));
}

template<class T,
         class = typename std::enable_if<!aux::is_array<T>()>::type>
auto adl_end(T&& x) -> decltype(aux::adl_end(std::forward<T>(x)))
{
    using std::end;
    return end(std::forward<T>(x));
}

template<typename T, std::size_t N>
T* adl_begin(T (&x)[N])
{
    return std::begin(x);
}

template<typename T, std::size_t N>
T* adl_end(T (&x)[N])
{
    return std::end(x);
}

このコードはかなり怪物です。うまくいけば、C++14 では、これが難解でなくなる可能性があります。

template<typename T>
concept bool Not_array()
{
    using type = std::remove_reference_t<T>;
    return !std::is_array<type>::value;
}

decltype(auto) adl_begin(Not_array&& x)
{
    using std::begin;
    return begin(std::forward<Not_array>(x));
}

decltype(auto) adl_end(Not_array&& x)
{
    using std::end;
    return end(std::forward<Not_array>(x));
}

template<typename T, std::size_t N>
T* adl_begin(T (&x)[N])
{
    return std::begin(x);
}

template<typename T, std::size_t N>
T* adl_end(T (&x)[N])
{
    return std::end(x);
}
于 2013-09-13T07:22:57.693 に答える
6

免責事項:衒学的なタイプ (または衒学的になりたい場合は、衒学者) については、ここでは通常、 「過負荷」という言葉を「 andと doという名前を持つ関数を作成する」と呼んでいます。beginendusing std::begin; using std::end;、私を信じてください、私が書くのはまったく退屈ではありませんが、読むのは非常に難しく、読むのは冗長です.:p.


基本的に、そのような手法の可能なユースケースを紹介し、後で結論を述べます.

ケース 1 -メソッドbeginendメソッドが標準コンテナーのように機能しない

std::beginandstd::end関数をオーバーロードする必要がある状況の 1 つは、オブジェクトの要素への反復子のようなアクセスを提供する以外の別の方法で型のbeginandメソッドを使用していて、 and のオーバーロードを呼び出したい場合です。反復に使用される begin メソッドと end メソッド。endstd::beginstd::end

struct weird_container {
   void begin() { std::cout << "Start annoying user." }
   void end() { std::cout << "Stop annoying user." }

   iterator iter_begin() { /* return begin iterator */ }
   iterator iter_end() { /* return end iterator */ }
};


auto begin(weird_container& c) {
   return c.iter_begin();
}

auto end(weird_container& c) {
   return c.iter_end();
}

weird_containerただし、 range-forのルールに従って、のオブジェクトweird_container::begin()と一緒に使用すると range-for が壊れてしまうようなクレイジーなことを行うべきではありませんweird_container::end()

したがって、このケースでは、言語の非常に便利な機能の 1 つが壊れてしまうため、あなたが提案したものを使用しないという議論が生じます。

ケース 2 -beginメソッドendがまったく定義されていない

beginもう 1 つのケースは、 andendメソッドを定義しない場合です。これは、クラス インターフェイスを変更せずに型を反復可能に拡張する場合に、より一般的で適切なケースです。

struct good_ol_type {
   ...
   some_container& get_data();
   ...
};

auto begin(good_ol_type& x) {
   return x.get_data().begin();
}

auto end(good_ol_type& x) {
   return x.get_data().end();
}

good_ol_typeこれにより、インターフェイスを実際に変更することなく、いくつかの気の利いた機能 (アルゴリズム、range-for など) を使用できるようになります! これは、非メンバー非フレンド関数を通じて型の機能を拡張するという Herb Sutter の推奨事項と一致しています。

std:;beginこれは良いケースで、実際にandをオーバーロードしたい場合ですstd::end

結論

誰かが最初のケースのようなことをするのを見たことがないので(私の例を除いて)、提案したものを使用してオーバーロードstd::beginし、std::end適用可能な場合はどこでも使用したいと思うでしょう。


beginここでは、 andendメソッドと、メソッドとは異なることを行うbeginand関数の両方を定義したケースは含めませんでした。endこのような状況は、デバッガーを掘り下げたり、新しいテンプレートエラーを読んだりした経験があまりないプログラマーによって、不自然で、不適切な形式で、および/または行われたと思います。

于 2013-09-13T07:52:02.720 に答える
1

some_container が標準コンテナの場合、 std:: プレフィックスは不要です

#include <iostream>
#include <vector>
#include <algorithm>
int main(){ 
       std::vector<int>v { 1, 7, 1, 3, 6, 7 };
       std::sort( begin(v), end(v) ); // here ADL search finds std::begin, std::end
}
于 2013-09-13T07:32:38.903 に答える
0

ドキュメントswap、あなたが参照するイディオムがstlライブラリの一般的な慣行であることを指定しています

標準ライブラリ (std 内) の多くのコンポーネントは、swap を非修飾の方法で呼び出して、このジェネリック バージョンの代わりに非基本型のカスタム オーバーロードを呼び出すことができるようにします。提供されたものは、このジェネリック バージョンに対する引数依存のルックアップによって選択されます。

beginとのドキュメントにはそのようなものはありませんend

このため、間違いなく

using std::begin;
using std::end;
some_algorithm(begin(some_container), end(some_container));

呼び出し規則ですが、これは標準アルゴリズムなどには適用されず、コードのみに適用される規則であることに注意する必要があります。

于 2013-09-13T07:17:08.313 に答える