web-dev-qa-db-ja.com

完全に一般的なswap()関数を定義しても大丈夫ですか?

次のスニペット:

_#include <memory>
#include <utility>

namespace foo
{
    template <typename T>
    void swap(T& a, T& b)
    {
        T tmp = std::move(a);
        a = std::move(b);
        b = std::move(tmp);
    }

    struct bar { };
}

void baz()
{
    std::unique_ptr<foo::bar> ptr;
    ptr.reset();
}
_

私のためにコンパイルされません:

_$ g++ -std=c++11 -c foo.cpp
In file included from /usr/include/c++/5.3.0/memory:81:0,
                 from foo.cpp:1:
/usr/include/c++/5.3.0/bits/unique_ptr.h: In instantiation of ‘void std::unique_ptr<_Tp, _Dp>::reset(std::unique_ptr<_Tp, _Dp>::pointer) [with _Tp = foo::bar; _Dp = std::default_delete<foo::bar>; std::unique_ptr<_Tp, _Dp>::pointer = foo::bar*]’:
foo.cpp:20:15:   required from here
/usr/include/c++/5.3.0/bits/unique_ptr.h:342:6: error: call of overloaded ‘swap(foo::bar*&, foo::bar*&)’ is ambiguous
  swap(std::get<0>(_M_t), __p);
      ^
In file included from /usr/include/c++/5.3.0/bits/stl_pair.h:59:0,
                 from /usr/include/c++/5.3.0/bits/stl_algobase.h:64,
                 from /usr/include/c++/5.3.0/memory:62,
                 from foo.cpp:1:
/usr/include/c++/5.3.0/bits/move.h:176:5: note: candidate: void std::swap(_Tp&, _Tp&) [with _Tp = foo::bar*]
     swap(_Tp& __a, _Tp& __b)
     ^
foo.cpp:7:10: note: candidate: void foo::swap(T&, T&) [with T = foo::bar*]
     void swap(T& a, T& b)
_

これは、swap()関数を非常に一般的であるため、_std::swap_と競合することを宣言した私のせいですか?

もしそうなら、それがケーニッヒルックアップによって引っ張られないようにfoo::swap()を定義する方法はありますか?

39
Tavian Barnes

問題は、libstdc ++によるunique_ptrの実装です。これは彼らの4.9.2ブランチからのものです:

https://gcc.gnu.org/onlinedocs/gcc-4.9.2/libstdc++/api/a01298_source.html#l00339

  338       void
  339       reset(pointer __p = pointer()) noexcept
  340       {
  341     using std::swap;
  342     swap(std::get<0>(_M_t), __p);
  343     if (__p != pointer())
  344       get_deleter()(__p);
  345       }

ご覧のとおり、修飾されていないスワップ呼び出しがあります。それでは、libcxx(libc ++)の実装を見てみましょう。

https://git.io/vKzhF

_LIBCPP_INLINE_VISIBILITY void reset(pointer __p = pointer()) _NOEXCEPT
{
    pointer __tmp = __ptr_.first();
    __ptr_.first() = __p;
    if (__tmp)
        __ptr_.second()(__tmp);
}

_LIBCPP_INLINE_VISIBILITY void swap(unique_ptr& __u) _NOEXCEPT
    {__ptr_.swap(__u.__ptr_);}

swap内でresetを呼び出したり、修飾されていないスワップ呼び出しを使用したりすることはありません。


Dypの答え は、libstdc++が準拠している理由だけでなく、標準ライブラリでswapを呼び出す必要があるときにコードが破損する理由についてもかなり確実な内訳を提供します。引用するには TemplateRex

特定のタイプのみを含む非常に特定の名前空間で、このような一般的なswapテンプレートを定義する理由はありません。 foo::barに対して非テンプレートswapオーバーロードを定義するだけです。一般的なスワッピングはstd::swapのままにし、特定のオーバーロードのみを提供します。 ソース

例として、これはコンパイルされません:

std::vector<foo::bar> v;
std::vector<foo::bar>().swap(v);

古い標準ライブラリ/ GCC(CentOSなど)を備えたプラットフォームをターゲットにしている場合は、このような落とし穴を避けるために、車輪の再発明ではなくBoostを使用することをお勧めします。

14
user6253369

この手法は、foo::swap()がADLによって検出されないようにするために使用できます。

_namespace foo
{
    namespace adl_barrier
    {
        template <typename T>
        void swap(T& a, T& b)
        {
            T tmp = std::move(a);
            a = std::move(b);
            b = std::move(tmp);
        }
    }

    using namespace adl_barrier;
}
_

これは、Boost.Rangeの独立したbegin()/end()関数が定義される方法です。質問する前に同様のことを試しましたが、代わりに_using adl_barrier::swap;_を実行しましたが、機能しません。

質問のスニペットがそのまま機能するかどうかについては、よくわかりません。私が見ることができる1つの複雑さは、_unique_ptr_がpointerからのカスタムDeleterタイプを持つことができることです。これは、通常のusing std::swap; swap(a, b);イディオムと交換する必要があります。そのイディオムは、質問の_foo::bar*_では明らかに壊れています。

12
Tavian Barnes