いくつかのアプローチがあり、それぞれに長所と短所があります。費用便益分析による以下の 3 つのアプローチ。
カスタム非会員によるADL begin()
/end()
最初の代替案は、名前空間内に非メンバーbegin()
およびend()
関数テンプレートを提供legacy
して、必要な機能をそれを提供できる任意のクラスまたはクラス テンプレートに後付けしますが、たとえば間違った命名規則があります。呼び出しコードは、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()
- C スタイルの配列で動作します
- メンバーを持たず、ソースコードの変更を必要とせずに、任意のクラステンプレートに対して機能するように後付けできます ( range-for ループでも!)。
legacy::Container<T>
.begin()
end()
短所: 多くの場所で using 宣言が必要です
std::begin
またstd::end
、C スタイルの配列のフォールバック オプションとして、すべての明示的な呼び出しスコープに持ち込む必要があります (テンプレート ヘッダーの潜在的な落とし穴と一般的な煩わしさ)。
カスタム非メンバーによる ADLadl_begin()
およびadl_end()
adl
2 番目の方法は、非メンバー関数テンプレートadl_begin()
およびを提供することによって、前のソリューションの using 宣言を別の名前空間にカプセル化するadl_end()
ことです。これは、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";
}
長所:完全に一般的に機能する一貫した呼び出し規約
- @Xeoの提案と同じ長所 +
- 繰り返される using 宣言はカプセル化されています (DRY)
短所:少し冗長
adl_begin()
/は/adl_end()
ほど簡潔ではないbegin()
end()
- それはおそらくそれほど慣用的ではありません(明示的ではありますが)
- 保留中の C++14 の戻り値の型推定は、名前空間を
std::begin
/で汚染しますstd::end
注:これが以前のアプローチを本当に改善するかどうかはわかりません。
明示的に修飾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()
- C スタイルの配列で動作します
短所:少し冗長で、レトロフィットは一般的ではなく、メンテナンスの問題です
std::begin()
/は/std::end()
よりも少し冗長ですbegin()
end()
- 非メンバー関数テンプレートの明示的な特殊化と
LegacyContainer
.begin()
end()
begin()
end()
namespace std
- メンバー関数を直接追加することによってのみ、クラステンプレート に後付けできます/のソースコード内(テンプレート用に利用可能)。関数テンプレートは部分的に特殊化できないため、ここでは このトリックは機能しません。
LegacyContainer<T>
begin()
end()
LegacyContainer<T>
namespace std
何を使う?
コンテナー自身の名前空間内の非メンバーbegin()
/を介した ADL アプローチは、慣用的な C++11 アプローチであり、特にレガシー クラスとクラス テンプレートの改造が必要な汎用関数の場合です。end()
これは、ユーザー提供の非メンバーswap()
関数と同じイディオムです。
標準コンテナーまたは C スタイルの配列のみを使用し、using 宣言を導入せずにどこでも呼び出すことができるコードstd::begin()
のstd::end()
場合、より冗長な呼び出しを犠牲にします。namespace std
このアプローチは後付けすることもできますが、 (クラス タイプの場合) またはインプレース ソース変更 (クラス テンプレートの場合) をいじる必要があります。それはできますが、メンテナンスの手間をかける価値はありません。
問題のコンテナーがコーディング時に既知である非汎用コードでは、標準コンテナーのみの ADL に依存し、C スタイルの配列を明示的に修飾std::begin
/することもできます。std::end
呼び出しの一貫性がいくらか失われますが、using 宣言を節約できます。