1

コンテナは次のとおりです。

namespace container_namespace
{

template <class element_type, class element_allocator_type = std::allocator<element_type> >
class container
{
    // stuff

    class iterator
    {
        // stuff
    };
};

}

ADL (Argument-dependent lookup) 経由でadvance(InputIt &, Distance N)使用できるようにするには、上記のどこで定義しますか。advance()main()

int main(int argc, char **argv)
{
    using namespace std;
    using namespace container_namespace;

    container<int> c;

    // Add elements to c here

    container<int>::iterator it = c.begin();
    advance(it, 20);
}

そして、advance()代わりにカスタム関数を選択しましたstd::advanceか? カスタムadvance()関数が iterator クラス内で定義されている例と、それが名前空間内で定義され、友情のみが iterator クラス内で宣言されている例を見てきました。ADLの使用を可能にするために正しいのはどれですか? SO に関する他の例は、この点で明確ではありませんでした。

4

4 に答える 4

3

最も安全な方法はfriendcontainerorを定義することだと思いますiteratornamespace container_namespaceこのように定義された関数は、ADL で見つけられるように に入れられます。

namespace container_namespace {
    template <class element_type, class element_allocator_type = std::allocator<element_type> >
    class container {
        //...
        template <typename Diff>
        friend void advance(iterator&, Diff) {
            //...
        }
    };
}

デモ

別のオプションとして、 で直接定義することもできますnamespace container_namespace。このようにして、実装で行われるように、すべてのコンテナに共通の実装を作成したり、さまざまなイテレータ カテゴリを処理するタグ ディスパッチを実装したりできますstd::advance

namespace container_namespace {
    template <typename Iter, typename Diff>
    void advance(Iter&, Diff) {
        std::cout << "ADL-ed advance\n";
    }
}

このアプローチの問題std::advanceは、範囲内にあるときにあいまいさを引き起こす可能性があることです (@TC に感謝): DEMO

advanceまた、次のように定義できないことに注意してください。

namespace container_namespace {
    template <typename element_type, typename element_allocator_type, typename Diff>
    void advance(typename container<element_type, element_allocator_type>::iterator&, Diff) {
        std::cout << "ADL-ed advance\n";
    }
}

最初の引数の型が失敗するためです (推定されないコンテキストを参照)。

于 2016-02-02T04:12:18.057 に答える
2

投稿された両方の回答は正しいですが (私は両方に賛成票を投じました)、将来これを見つけた人のために、もう少し詳しく説明したいと思います。

「友達」の意味

まず、「フレンド」は、クラス内の関数では異なる意味を持ちます。単なる関数宣言の場合は、指定された関数をクラスのフレンドとして宣言し、そのプライベート/保護されたメンバーへのアクセスを許可します。ただし、それが関数の実装である場合、その関数は (a) クラスのフレンドであり、(b) クラスのメンバーではなく、(c) 囲んでいる名前空間内からはアクセスできないことを意味します。すなわち。これは、引数依存ルックアップ(ADL)を介してのみアクセスできるグローバル関数になります。

たとえば、次のテスト コードを見てください。

#include <iostream>
#include <iterator>

namespace nsp
{

template <class element_type, class element_allocator_type = std::allocator<element_type> >
class test_container
{
private:
    element_type numbers[50];
    friend class iterator;

public:
    class iterator : public std::iterator<std::bidirectional_iterator_tag, element_type>
    {
    private: 
        element_type *i;

        template <class distance_type>
        friend void advance(iterator &it, distance_type n);

        friend typename std::iterator_traits<iterator>::difference_type distance(const iterator &first, const iterator &last)
        {
            return last.i - first.i;
        }


    public: 

        iterator(element_type &_i)
        {
            i = &(_i);
        }

        element_type & operator *()
        {
            return *i;
        }

        element_type & operator = (const iterator source)
        {
            i = source.i;
            return *this;
        }

        bool operator != (const iterator rh)
        {
            return i != rh.i;
        }

        iterator & operator ++()
        {
            ++i;
            return *this;
        }

        iterator & operator --()
        {
            --i;
            return *this;
        }
    };


    iterator begin()
    {
        return iterator(numbers[0]);
    }


    iterator end()
    {
        return iterator(numbers[50]);
    }


    template <class distance_type>
    friend void advance(iterator &it, distance_type n)
    {
        it.i += 2 * n;
    }

};


}


int main(int argc, char **argv)
{
    nsp::test_container<int> stuff;

    int counter = 0;

    for (nsp::test_container<int>::iterator it = stuff.begin(); it != stuff.end(); ++it)
    {
        *it = counter++;
    }

    nsp::test_container<int>::iterator it = stuff.begin(), it2 = stuff.begin();

    using namespace std;

    cout << *it << endl;

    ++it;

    cout << *it << endl;

    advance(it, 2);

    cout << *it << endl;

    std::advance(it, 2);

    cout << *it << endl;

    int distance_between = distance(it2, it);

    cout << distance_between << endl;

    cin.get();

    return 0;
}

from withinmain()advance()呼び出されると、ADL が機能し、クラス イテレータのカスタム Advance が呼び出されます。ただし、nsp::advance()またはnsp::test_container<int>::advance()stuff.advance()試すと、コンパイル エラー (「一致する関数呼び出しがありません」) が発生します。

テンプレートの問題

テンプレート関数のオーバーロードに優先して非テンプレート関数のオーバーロードが呼び出されることは事実ですが、これは ADL の使用には関係ありません。関数がテンプレートであるか非テンプレートであるかに関係なく、特定の型の正しいオーバーロードが呼び出されます。さらに、advance()具体的には距離型 (int、long int、long long int など) のテンプレート パラメーターが必要です。これをスキップすることはできません。コンパイラーがどの型から推論しようとしているかがわからないためです。 」であり、プログラマーがどのようなタイプをスローするかはわかりませんadvance()。幸いなことに、カスタム アドバンスとは異なる名前空間にある部分的な特殊化について心配する必要はなく、上記の例に示すように、ハードコードされたイテレータ型を使用してstd::advance()独自のものを簡単に実装できます。advance()

これは、イテレーター自体がテンプレートであり、パラメーターを受け取る場合でも機能します。パラメーターを事前テンプレートに含め、テンプレート化されたイテレーター型をそのようにハードコードするだけです。例えば。:

template <class element_type, class distance_type>
friend void advance(iterator<element_type>, distance_type distance);

その他のテンプレートの問題 (補足)

これは特に実装に関連するものではありませんがadvance()、一般的なクラス フレンド関数の実装に関連しています。上記の例では、非テンプレート関数distance()を iterator クラス内に直接実装しているのに気付くでしょう。一方、advance()テンプレート化された関数は iterator クラスの外側で test_container クラス内でフレンドとして宣言されています。これはポイントを説明するためのものです。

コンパイラがエラーをスローするため、クラスがテンプレート (またはテンプレートの一部) である場合、非テンプレート フレンド関数をフレンドであるクラスの外部に実装することはできません。ただし、テンプレート化された関数は、フレンド クラスに含まれる定義のみを使用して、クラスの外部で宣言advance() できます。このadvance()関数は、フレンド クラス内で直接実装することもできますが、この点を説明するために実装しないことにしました。

テンプレート フレンド関数パラメーターのシャドーイング

これは上記の例とは関係ありませんが、プログラマーがテンプレート フレンド関数に足を踏み入れる際の落とし穴になる可能性があります。テンプレート クラスと、そのクラスで動作するフレンド関数がある場合、関数定義とクラスでテンプレート パラメーターを指定する必要があることは明らかです。例えば:

template <class element_type, class element_allocator_type = std::allocator<element_type> >
class test_container
{
private:
    element_type numbers[50];

public:
    template <class element_type, class element_allocator_type>
    friend void do_stuff(test_container<element_type, element_allocator_type> &container)
    {
        numbers[1] = 5; // or whatever
    }

};

ただし、コンパイラは「element_type」と「element_allocator_type」に同じ名前を使用すると、test_container の定義で最初に使用されたテンプレート パラメーターの再定義であると見なし、エラーをスローするため、上記は機能しません。したがって、これらには別の名前を使用する必要があります。すなわち。これは機能します:

template <class element_type, class element_allocator_type = std::allocator<element_type> >
class test_container
{
private:
    element_type numbers[50];

public:
    template <class c_element_type, class c_element_allocator_type>
    friend void do_stuff(test_container<c_element_type, c_element_allocator_type> &container)
    {
        numbers[1] = 5; // or whatever
    }

};

それだけです-これに出くわした人がそれを利用できることを願っています-この情報のほとんどは、何らかの形、形、または形式でstackoverflow全体に広がっていますが、初心者にとってはそれらをまとめることが重要です.

[更新:] 上記のすべてを使用しても、「正しい」にもかかわらず、ADL を正しい関数に正しく解決するにはまだ十分ではない可能性があります。これは、clang、Microsoft Visual Studio 2010 ~ 2013、およびその他のバージョンでは、複雑なテンプレートで ADL を解決するのが困難であり、いずれにせよクラッシュまたはエラーをスローする可能性があるためです。この場合、イテレータ クラスに関連付けられている標準のコンテナ関数に単純に頼るのが賢明です。

于 2016-02-08T02:40:51.003 に答える