通常のルールに従って動作するカスタムコンテナクラスを作成する場合(つまり、STLアルゴリズムで動作する、正常に動作するジェネリックコードで動作するなど)、C++ 03ではイテレータサポートとメンバーの開始/終了関数を実装するだけで十分でした。
C++ 11では、範囲ベースのforループとstd :: begin/endの2つの新しい概念が導入されています。範囲ベースのforループは、メンバーの開始/終了関数を理解するため、C++ 03コンテナーは、すぐに使用できる範囲ベースのforをサポートします。アルゴリズムの場合、推奨される方法(HerbSutterによる「最新のC++コードの記述」による)は、メンバー関数の代わりにstd :: beginを使用することです。
ただし、この時点で質問する必要があります-完全修飾begin()関数(つまり、std :: begin(c))を呼び出すか、ADLに依存してbegin(c)を呼び出すための推奨される方法ですか?
この特定のケースではADLは役に立たないようです-可能であればstd :: begin(c)がc.begin()に委任するため、通常のADLの利点は適用されないようです。そして、誰もがADLに依存し始めた場合、すべてのカスタムコンテナーは、必要な名前空間に追加のbegin()/ end()フリー関数を実装する必要があります。ただし、いくつかの情報源は、開始/終了の修飾されていない呼び出しが推奨される方法であることを示唆しているようです(つまり、 https://svn.boost.org/trac/boost/ticket/6357 )。
では、C++ 11の方法は何ですか?コンテナライブラリの作成者は、名前空間stdを使用せずに、修飾されていない開始/終了呼び出しをサポートするために、クラスに追加の開始/終了関数を作成する必要があります。またはstd :: begin;?
いくつかのアプローチがあり、それぞれに長所と短所があります。費用便益分析による以下の3つのアプローチ。
begin()
/end()
最初の選択肢は、legacy
名前空間内に非メンバーのbegin()
およびend()
関数テンプレートを提供して、必要な機能を、それを提供できる任意のクラスまたはクラステンプレートに後付けしますが、たとえば間違った命名規則。呼び出し元のコードは、ADLに依存してこれらの新しい関数を見つけることができます。サンプルコード(@Xeoによるコメントに基づく):
_// LegacyContainerBeginEnd.h
namespace legacy {
// retro-fitting begin() / end() interface on legacy
// Container class template with incompatible names
template<class C>
auto begin(Container& c) -> decltype(c.legacy_begin())
{
return c.legacy_begin();
}
// similarly for begin() taking const&, cbegin(), end(), cend(), etc.
} // namespace legacy
// print.h
template<class C>
void print(C const& c)
{
// bring into scope to fall back on for types without their own namespace non-member begin()/end()
using std::begin;
using std::end;
// works for Standard Containers, C-style arrays and legacy Containers
std::copy(begin(c), end(c), std::ostream_iterator<decltype(*begin(c))>(std::cout, " ")); std::cout << "\n";
// alternative: also works for Standard Containers, C-style arrays and legacy Containers
for (auto elem: c) std::cout << elem << " "; std::cout << "\n";
}
_
長所:完全に一般的に機能する一貫性のある簡潔な呼び出し規約
.begin()
および.end()
を定義するすべての標準コンテナーおよびユーザータイプで機能します.begin()
を持たないクラステンプレート _legacy::Container<T>
_に対して(範囲-forループ!) end()
ソースコードの変更を必要としない短所:使用する必要があります-多くの場所での宣言
std::begin
_および_std::end
_は、Cスタイルの配列のフォールバックオプションとして、すべての明示的な呼び出しスコープに組み込まれている必要があります(テンプレートヘッダーの潜在的な落とし穴と一般的な迷惑)adl_begin()
およびadl_end()
によるADL2番目の方法は、非メンバー関数テンプレートadl_begin()
とadl_end()
を提供することにより、前のソリューションのusing宣言を個別のadl
名前空間にカプセル化することです。 ADLからも見つけることができます。サンプルコード(@Yakkによるコメントに基づく):
_// LegacyContainerBeginEnd.h
// as before...
// ADLBeginEnd.h
namespace adl {
using std::begin; // <-- here, because otherwise decltype() will not find it
template<class C>
auto adl_begin(C && c) -> decltype(begin(std::forward<C>(c)))
{
// using std::begin; // in C++14 this might work because decltype() is no longer needed
return begin(std::forward<C>(c)); // try to find non-member, fall back on std::
}
// similary for cbegin(), end(), cend(), etc.
} // namespace adl
using adl::adl_begin; // will be visible in any compilation unit that includes this header
// print.h
# include "ADLBeginEnd.h" // brings adl_begin() and adl_end() into scope
template<class C>
void print(C const& c)
{
// works for Standard Containers, C-style arrays and legacy Containers
std::copy(adl_begin(c), adl_end(c), std::ostream_iterator<decltype(*adl_begin(c))>(std::cout, " ")); std::cout << "\n";
// alternative: also works for Standard Containers, C-style arrays and legacy Containers
// does not need adl_begin() / adl_end(), but continues to work
for (auto elem: c) std::cout << elem << " "; std::cout << "\n";
}
_
長所:完全に一般的に機能する一貫した呼び出し規約
短所:少し冗長
adl_begin()
/adl_end()
はbegin()
/end()
ほど簡潔ではありませんstd::begin
_/_std::end
_で名前空間も汚染します[〜#〜] note [〜#〜]:これが前のアプローチを本当に改善するかどうかはわかりません。
std::begin()
またはstd::end()
を明示的に修飾するとにかくbegin()
/end()
の冗長性が放棄されたら、std::begin()
/std::end()
の修飾された呼び出しに戻ってみませんか?コード例:
_// LegacyIntContainerBeginEnd.h
namespace std {
// retro-fitting begin() / end() interface on legacy IntContainer class
// with incompatible names
template<>
auto begin(legacy::IntContainer& c) -> decltype(c.legacy_begin())
{
return c.legacy_begin();
}
// similary for begin() taking const&, cbegin(), end(), cend(), etc.
} // namespace std
// LegacyContainer.h
namespace legacy {
template<class T>
class Container
{
public:
// YES, DOCUMENT REALLY WELL THAT THE EXISTING CODE IS BEING MODIFIED
auto begin() -> decltype(legacy_begin()) { return legacy_begin(); }
auto end() -> decltype(legacy_end()) { return legacy_end(); }
// rest of existing interface
};
} // namespace legacy
// print.h
template<class C>
void print(C const& c)
{
// works for Standard Containers, C-style arrays as well as
// legacy::IntContainer and legacy::Container<T>
std::copy(std::begin(c), std::end(c), std::ostream_iterator<decltype(*std::begin(c))>(std::cout, " ")); std::cout << "\n";
// alternative: also works for Standard Containers, C-style arrays and
// legacy::IntContainer and legacy::Container<T>
for (auto elem: c) std::cout << elem << " "; std::cout << "\n";
}
_
長所:ほぼ一般的に機能する一貫した呼び出し規約
.begin()
および.end()
を定義するすべての標準コンテナーおよびユーザータイプで機能します短所:少し冗長で後付けは一般的ではなく、保守の問題です
std::begin()
/std::end()
はbegin()
/end()
よりも少し冗長です.begin()
を持たないclassLegacyContainer
に対してのみ、機能するように改造できます(range-for loops!) __(SOMECODE)の非メンバー関数テンプレートend()
およびbegin()
の明示的な特殊化を提供することにより、end()
(およびソースコードはありません!) __namespace std
LegacyContainer<T>
_のソースコード内にメンバー関数begin()
/end()
を直接追加することによってのみ、クラステンプレート _LegacyContainer<T>
_に後付けできます。テンプレート用に利用可能です)。関数テンプレートを部分的に特殊化できないため、_namespace std
_トリックはここでは機能しません。コンテナ自体の名前空間内の非メンバーbegin()
/end()
を介したADLアプローチは、特にレガシークラスとクラステンプレートの改良を必要とする汎用関数の場合、慣用的なC++ 11アプローチです。これは、ユーザーが非メンバーのswap()
関数を提供する場合と同じイディオムです。
標準コンテナまたはCスタイルの配列のみを使用するコードの場合、std::begin()
およびstd::end()
は、using-declarationsを導入せずにどこでも呼び出すことができますが、より詳細な呼び出しが必要になります。このアプローチは後付けすることもできますが、_namespace std
_(クラスタイプの場合)またはインプレースソース変更(クラステンプレートの場合)をいじる必要があります。それは可能ですが、メンテナンスの手間をかける価値はありません。
問題のコンテナがコーディング時に認識されている非ジェネリックコードでは、標準コンテナのみをADLに依存し、Cスタイルの配列に対して_std::begin
_/_std::end
_を明示的に修飾することもできます。呼び出しの一貫性は失われますが、using-declarationsを節約できます。