ジェネリック関数では、次のイディオムを使用します。
_template<class It1, class It2>
void do_something(It1 first, It1 second, It2 d_first){
... other stuff here...
using std::copy;
copy(first, second, d_first);
}
_
_do_something
_は、他のライブラリに固有のことを何も知らないはずのジェネリック関数です(おそらく_std::
_を除く)。
ここで、名前空間N
に複数のイテレータがあるとします。
_namespace N{
struct itA{using trait = void;};
struct itB{using trait = void;};
struct itC{using trait = void;};
}
_
この名前空間でこれらのイテレータのコピーをオーバーロードしたいと思います。当然私はします:
_namespace N{
template<class SomeN1, class SomeN2>
SomeN2 copy(SomeN1 first, SomeN1 last, SomeN2 d_first){
std::cout << "here" << std::endl;
}
}
_
ただし、_do_something
_、_N::A
_、または_N::B
_引数を指定して_N::C
_を呼び出すと、_N::copy
_と同じ名前空間にあるにもかかわらず、「あいまいなコピー呼び出し」が発生します。 。
上記の元の関数のコンテキストで_std::copy
_に勝つ方法はありますか?
テンプレート引数に制約を設定すると、_N::copy
_が優先されます。
_namespace N{
template<class SomeN1, class SomeN2, typename = typename SomeN1::trait>
SomeN2 copy(SomeN1 first, SomeN1 last, SomeN2 d_first){
std::cout << "here" << std::endl;
}
}
_
しかし、それは役に立ちません。
_std::copy
_ではなく引数の名前空間のコピーを優先するようにコピーする一般的な呼び出しに対して他にどのような回避策を試すことができますか?
完全なコード:
_#include<iostream>
#include<algorithm>
namespace N{
struct A{};
struct B{};
struct C{};
}
namespace N{
template<class SomeN1, class SomeN2>
SomeN2 copy(SomeN1 first, SomeN1 last, SomeN2 d_first){
std::cout << "here" << std::endl;
}
}
template<class It1, class It2>
void do_something(It1 first, It1 second, It2 d_first){
using std::copy;
copy(first, second, d_first); // ambiguous call when It is from namespace N (both `std::copy` and `N::copy` could work.
}
int main(){
N::A a1, a2, a3;
do_something(a1, a2, a3);
}
_
典型的なエラーメッセージは
error: call of overloaded ‘copy(N::A&, N::A&, N::A&)’ is ambiguous
C++の概念は、制約が少ないよりも制約が多い関数呼び出しを優先することで、ここで役立つと思うのは正しいですか?
イテレータクラスでcopy()
を public friend function として宣言できます。これは、部分的な特殊化(関数では不可能)の代わりとして機能するため、より特殊化されているため、過負荷の解決によって優先されます。
_#include <iostream>
#include <algorithm>
#include <vector>
namespace N
{
template<class SomeN1, class SomeN2>
SomeN2 copy(SomeN1 first, SomeN1 last, SomeN2 d_first)
{
std::cout << "here" << std::endl;
return d_first;
}
template <class T>
struct ItBase
{
template <class SomeN2>
friend SomeN2 copy(T first, T last, SomeN2 d_first)
{
return N::copy(first, last, d_first);
}
};
struct A : ItBase<A>{};
struct B : ItBase<B>{};
struct C : ItBase<C>{};
}
template<class It1, class It2>
void do_something(It1 first, It1 second, It2 d_first){
using std::copy;
copy(first, second, d_first);
}
int main(){
N::A a1, a2, a3;
std::cout << "do something in N:" << std::endl;
do_something(a1, a2, a3);
std::vector<int> v = {1,2,3};
std::vector<int> v2(3);
std::cout << "do something in std:" << std::endl;
do_something(std::begin(v), std::end(v), std::begin(v2));
for (int i : v2)
std::cout << i;
std::cout << std::endl;
}
_
このデモ を参照して、機能することを確認してください。
すべてのイテレータに必要な友達を宣言する共通の基本クラスを導入しました。したがって、タグを宣言する代わりに、試したように、ItBase
から継承する必要があります。
注:N::copy()
がN
内のこれらのイテレーターのみで機能することになっている場合、これらのフレンド関数はとにかくN
で公開されるため、不要になる可能性があります(それらが無料の機能だった場合)。
コメントでは、N
のイテレータがとにかく共通の基本クラスを持っている場合、この基本クラスで_N::copy
_を宣言することが提案されています。
_namespace N
{
template <class SomeN2>
SomeN2 copy(ItBase first, ItBase last, SomeN2 d_first) { ... }
}
_
残念ながら、これは目的のインスタンスとは逆の効果をもたらします。A
のインスタンスを渡すと、順番にダウンキャストする必要があるため、_std::copy
_は常に_N::copy
_よりも優先されます。 _N::copy
_に一致する一方で、_std::copy
_にはキャストは必要ありません。 ここ 明らかに_std::copy
_が呼び出されようとしていることがわかります(_N::A
_にはいくつかのtypedefがないため、エラーが発生します)。
したがって、_N::copy
_の署名に共通の基本クラスを利用することはできません。ソリューションで1つを使用した唯一の理由は、コードの重複を避けるためでした(すべてのイテレータークラスでフレンド関数を宣言する必要があります)。私のItBase
は過負荷の解決にまったく参加していません。
ただし、イテレータに_N::copy
_の実装で使用したいいくつかの共通メンバー(共通の基本クラスから派生したかどうかは重要ではない)がある場合は、私のソリューションでそれを行うことができます。上記のように:
_namespace N
{
template <class T>
struct ItBase
{
template <class SomeN2>
friend SomeN2 copy(T first, T last, SomeN2 d_first)
{
first.some_member();
last.some_member();
return d_first;
}
};
struct A : ItBase<A>{ void some_member() {} };
struct B : ItBase<B>{ void some_member() {} };
struct C : ItBase<C>{ void some_member() {} };
}
_
ここ それがどのように機能するかを参照してください。
同じ行で、A、B、Cの動作が共通している場合は、何らかの方法でパラメーター化された共通のテンプレートクラスに置き換えることができます。
_namespace N
{
template <class T, int I>
struct ItCommon
{
...
};
using A = ItCommon<double,2>;
using B = ItCommon<int, 3>;
using C = ItCommon<char, 5>;
}
...
namespace N{
template<class T, int I, class Other>
SomeN2 copy(ItCommon<T, I> first, ItCommon<T, I> last, Other){
...
}
}
_
この(友達ではない)copy
関数は_std::copy
_よりも確実に制約があり、ADLのため、引数の1つがN
名前空間に属している場合は優先度が高くなります。また、友達ではないため、このcopy
関数はオプションのコンポーネントです。
考えられる解決策の1つは、別の関数テンプレート名と型識別子を使用して、引数に依存する名前のルックアップで、引数の名前空間で関連する関数を見つけることです。
template<class T> struct Tag {};
template<class T> Tag<void> tag(T const&);
template<class It1, class It2>
void mycopy(It1 first, It1 second, It2 d_first, Tag<void>) {
std::cout << "std::copy\n";
}
template<class It1, class It2>
void mycopy(It1 first, It1 second, It2 d_first) {
mycopy(first, second, d_first, decltype(tag(first)){}); // Discriminate by the type of It1.
}
namespace N{
struct itA{using trait = void;};
Tag<itA> tag(itA);
template<class It1, class It2>
void mycopy(It1 first, It1 second, It2 d_first, Tag<itA>) {
std::cout << "N::mycopy\n";
}
}
int main() {
char* p = 0;
mycopy(p, p, p); // calls std::copy
N::itA q;
mycopy(q, q, q); // calls N::mycopy
}
C++ 11では、タグディスパッチを使用できます。カスタムイテレータに少し変更を加えることができれば、実装が少し簡単になります。
_#include <iostream>
#include <algorithm>
#include <vector>
#include <type_traits>
// indicates that the type doesn't have a tag type (like pointers and standard iterators)
struct no_tag{};
namespace detail
{
template <typename T>
auto tag_helper(int) -> typename T::tag;
template <typename>
auto tag_helper(long) -> no_tag;
}
// get T::tag or no_tag if T::tag isn't defined.
template <typename T>
using tag_t = decltype(detail::tag_helper<T>(0));
namespace N
{
struct my_iterator_tag {};
struct A{ using tag = my_iterator_tag; };
struct B{ using tag = my_iterator_tag; };
struct C{ using tag = my_iterator_tag; };
}
namespace N
{
template<class SomeN1, class SomeN2>
SomeN2 copy_helper(SomeN1 first, SomeN1 last, SomeN2 d_first, no_tag)
{
std::cout << "calling std::copy\n";
return std::copy(std::forward<SomeN1>(first), std::forward<SomeN1>(last), std::forward<SomeN2>(d_first));
}
template<class SomeN1, class SomeN2>
SomeN2 copy_helper(SomeN1 first, SomeN1 last, SomeN2 d_first, my_iterator_tag)
{
// your custom copy
std::cout << "custom copy function\n";
return {};
}
template<class SomeN1, class SomeN2>
SomeN2 copy(SomeN1 first, SomeN1 last, SomeN2 d_first)
{
return copy_helper(std::forward<SomeN1>(first), std::forward<SomeN1>(last), std::forward<SomeN2>(d_first), tag_t<SomeN1>{});
}
}
template<class It1, class It2>
void do_something(It1 first, It1 second, It2 d_first)
{
N::copy(first, second, d_first);
}
int main()
{
N::A a1, a2, a3;
std::cout << "using custom iterator: ";
do_something(a1, a2, a3);
std::cout << "using vector iterator: ";
std::vector<int> v;
do_something(std::begin(v), std::end(v), std::begin(v));
std::cout << "using pointer: ";
int* ptr = new int[10];
do_something(ptr, ptr + 5, ptr);
return 0;
}
_
まず、カスタムイテレータをtag
型に変更します(_iterator_category
_との混同を避けるために名前を変更する可能性があります)。 tag
は任意のタイプにすることができ、_copy_helper
_でタグとして使用するタイプと一致する必要があります。
次に、このtag
型にアクセスできるようにする型、またはtag
が存在しない場合はデフォルトの型にフォールバックできる型を定義します。これは、カスタムイテレータと標準のイテレータおよびポインタを区別するのに役立ちます。私が使用するデフォルトのタイプは_no_tag
_です。 _tag_t
_は、SFINAEと過負荷解決を使用してこの機能を提供します。 2つの宣言を持つ関数tag_helper(0)
を呼び出します。最初のものは_T::tag
_を返し、2番目のものは_no_tag
_を返します。 int
はlong
よりも_0
_に適しているため、tag_helper(0)
を呼び出すと常に最初のバージョンが使用されます。これは、常に最初に_T::tag
_にアクセスしようとすることを意味します。ただし、これが不可能な場合(_T::tag
_が定義されていない場合)、SFINAEが起動し、tag_helper(int)
をスキップしてtag_helper(long)
を選択します。
最後に、タグごとにコピー関数(_copy_helper
_と呼びます)と、利便性のためのラップアラウンドとして別のコピー関数(_N::copy
_を使用)を実装する必要があります。次に、ラッパー関数は適切なタグタイプを作成し、正しいヘルパー関数を呼び出します。
ここ は実例です。
コードを少し移動すると、名前空間N
を切断して、ADLに依存できます。
_#include <iostream>
#include <algorithm>
#include <vector>
#include <type_traits>
// indicates that the type doesn't have a tag type (like pointers and standard iterators)
struct no_tag{};
namespace detail
{
template <typename T>
auto tag_helper(int) -> typename T::tag;
template <typename>
auto tag_helper(long) -> no_tag;
}
// get T::tag or no_tag if T::tag isn't defined.
template <typename T>
using tag_t = decltype(detail::tag_helper<T>(0));
namespace N
{
struct my_iterator_tag {};
struct A{ using tag = my_iterator_tag; };
struct B{ using tag = my_iterator_tag; };
struct C{ using tag = my_iterator_tag; };
template<class SomeN1, class SomeN2>
SomeN2 copy_helper(SomeN1 first, SomeN1 last, SomeN2 d_first, my_iterator_tag)
{
// your custom copy
std::cout << "custom copy function\n";
return {};
}
}
template<class SomeN1, class SomeN2>
SomeN2 copy_helper(SomeN1 first, SomeN1 last, SomeN2 d_first, no_tag)
{
std::cout << "calling std::copy\n";
return std::copy(std::forward<SomeN1>(first), std::forward<SomeN1>(last), std::forward<SomeN2>(d_first));
}
template<class It1, class It2>
void do_something(It1 first, It1 second, It2 d_first)
{
copy_helper(std::forward<It1>(first), std::forward<It1>(second), std::forward<It2>(d_first), tag_t<It1>{});
}
int main()
{
N::A a1, a2, a3;
std::cout << "using custom iterator: ";
do_something(a1, a2, a3);
std::cout << "using vector iterator: ";
std::vector<int> v;
do_something(std::begin(v), std::end(v), std::begin(v));
std::cout << "using pointer: ";
int* ptr = new int[10];
do_something(ptr, ptr + 5, ptr);
return 0;
}
_
(これらのメモは@sebrockmの回答に対する私の編集に統合されています)
議論のために、私は別のオプションで自分の質問に対する答えを書きます。
すべてのN::
クラスを別のテンプレートクラス(ここではwrap
と呼びます)でラップする必要があるため、あまり良くありません。良いことは、do_something
クラスもN
クラスも特別なN::copy
について知る必要があるということです。代償として、main
呼び出し元はN::
クラスを明示的にラップする必要があります。これは醜いですが、システム全体について知っておく必要がある唯一のコードであるため、結合の観点からは問題ありません。 。
#include <iostream>
#include <algorithm>
#include <vector>
namespace N{
struct A{};
struct B{};
struct C{};
}
namespace N{
template<class S> struct wrap : S{};
template<class SomeN1, class SomeN2>
SomeN2 copy(wrap<SomeN1> first, wrap<SomeN1> last, wrap<SomeN2> d_first)
{
std::cout << "here" << std::endl;
return d_first;
}
}
template<class It1, class It2>
void do_something(It1 first, It1 second, It2 d_first){
using std::copy;
copy(first, second, d_first);
}
int main(){
N::wrap<N::A> a1, a2, a3;
std::cout << "do something in N:" << std::endl;
do_something(a1, a2, a3);
std::vector<int> v = {1,2,3};
std::vector<int> v2(3);
std::cout << "do something in std:" << std::endl;
do_something(std::begin(v), std::end(v), std::begin(v2));
for (int i : v2)
std::cout << i;
std::cout << std::endl;
}
OK、@ paler123に基づいて構築しますが、既存の型をチェックせず、It1
は代わりにポインタです:
namespace N{
struct A{};
struct B{};
struct C{};
}
namespace N{
template<class SomeN1, class SomeN2>
SomeN2 copy(SomeN1, SomeN1, SomeN2 c){
std::cout << "here" << std::endl;
return c;
}
}
template<class It1, class It2>
void do_something(It1 first, It1 second, It2 d_first){
if constexpr (std::is_pointer_v<It1>) {
std::copy(first, second, d_first);
}
else
{
copy(first, second, d_first);
}
}
int main(){
N::A a1, a2, a3;
do_something(a1, a2, a3);
int* b1, *b2, *b3;
do_something(b1, b2, b3);
}
それでもC++ 17ですが、ポインターの場合は、明示的なstd::copy
それ以外の場合は、ADLに依存します。
一般的に、あなたの問題は設計上の問題です。使用したいstd::copy
N
からのオブジェクトを除くすべての場合。その場合、ADLが機能することを期待します。しかし、あなたが強制したようにstd::copy
、適切なADLのオプションを削除します。すべてを手に入れることはできず、コードを再設計する必要があります。