web-dev-qa-db-ja.com

過負荷優先度の高い標準的な関数の書き方

ジェネリック関数では、次のイディオムを使用します。

_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++の概念は、制約が少ないよりも制約が多い関数呼び出しを優先することで、ここで役立つと思うのは正しいですか?

17
alfC

イテレータクラスで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関数はオプションのコンポーネントです。

4
sebrockm

考えられる解決策の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
}
3

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_を返します。 intlongよりも_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;
}
_
2
Timo

(これらのメモは@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;
}
1
alfC

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::copyNからのオブジェクトを除くすべての場合。その場合、ADLが機能することを期待します。しかし、あなたが強制したようにstd::copy、適切なADLのオプションを削除します。すべてを手に入れることはできず、コードを再設計する必要があります。

1