2

I am trying to play around with copy and swap idiom to understand it better, so I have defined the following class:

#include <iostream>
#include <algorithm>
#include <utility>

class dumb_array
{
public:
    dumb_array(std::size_t size = 0)
        : mSize(size)
        , mArray(size ? new int[size] : nullptr)
        {}
    
    dumb_array(const dumb_array& rhs)
        : mSize(rhs.mSize)
        , mArray(mSize ? new int[mSize]() : nullptr)
    {
        std::copy(rhs.mArray, rhs.mArray + mSize, mArray);
    }

    friend void swap(dumb_array& lhs, dumb_array& rhs) noexcept
    {
        std::swap(lhs.mSize, rhs.mSize);
        std::swap(lhs.mArray, rhs.mArray);
    }

    dumb_array& operator=(dumb_array rhs)
    {
        std::swap(*this, rhs);
        return *this;
    }

    dumb_array(dumb_array&& rhs) noexcept
        : dumb_array()
    {
        swap(*this, rhs);
    }

    dumb_array& operator=(dumb_array&& rhs) noexcept
    {
        if (this != &rhs)
            swap(*this, rhs);
        return *this;
    }

    int& operator[](const std::size_t& idx)
    {
        return mArray[idx];
    }

    ~dumb_array()
    {
        delete[] mArray;
    }

private:
    std::size_t mSize{0};
    int* mArray{nullptr};
};

int main()
{
    dumb_array a1{3};
    a1[0] = 2; a1[1] = 3; a1[2] = 4;
    std::cout << a1[0] << "\t" << a1[1] << "\t" << a1[2] << std::endl;

    dumb_array a2{a1};
    std::cout << a2[0] << "\t" << a2[1] << "\t" << a2[2] << std::endl;

    dumb_array a3;
    a3 = a2;
    std::cout << a3[0] << "\t" << a3[1] << "\t" << a3[2] << std::endl;

    return 0;
}

As you can notice, instead of friend swap function I have used std::swap inside copy assignment operator, because I already have both move constructor and move assignment operators defined which means that std::swap should be able to move dump_array object. The thing is that I am getting compile error when using std::swap function:

<source>: In member function 'dumb_array& dumb_array::operator=(dumb_array)':
<source>:28:18: error: no matching function for call to 'swap(dumb_array&, dumb_array&)'
   28 |         std::swap(*this, rhs);
      |         ~~~~~~~~~^~~~~~~~~~~~
In file included from /opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/exception:164,
                 from /opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/ios:41,
                 from /opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/ostream:40,
                 from /opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/iostream:41,
                 from <source>:1:
/opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/bits/exception_ptr.h:230:5: note: candidate: 'void std::__exception_ptr::swap(exception_ptr&, exception_ptr&)'
  230 |     swap(exception_ptr& __lhs, exception_ptr& __rhs)
      |     ^~~~
/opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/bits/exception_ptr.h:230:25: note:   no known conversion for argument 1 from 'dumb_array' to 'std::__exception_ptr::exception_ptr&'
  230 |     swap(exception_ptr& __lhs, exception_ptr& __rhs)
      |          ~~~~~~~~~~~~~~~^~~~~
In file included from /opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/bits/exception_ptr.h:41:
/opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/bits/move.h:189:5: note: candidate: 'template<class _Tp> constexpr std::_Require<std::__not_<std::__is_tuple_like<_Tp> >, std::is_move_constructible<_Tp>, std::is_move_assignable<_Tp> > std::swap(_Tp&, _Tp&)'
  189 |     swap(_Tp& __a, _Tp& __b)
      |     ^~~~
/opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/bits/move.h:189:5: note:   template argument deduction/substitution failed:
In file included from /opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/bits/move.h:37:
/opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/type_traits: In substitution of 'template<bool _Cond, class _Tp> using std::__enable_if_t = typename std::enable_if::type [with bool _Cond = false; _Tp = void]':
/opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/type_traits:2224:11:   required by substitution of 'template<class ... _Cond> using std::_Require = std::__enable_if_t<std::__and_<_Bn>::value> [with _Cond = {std::__not_<std::__is_tuple_like<dumb_array> >, std::is_move_constructible<dumb_array>, std::is_move_assignable<dumb_array>}]'
/opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/bits/move.h:189:5:   required by substitution of 'template<class _Tp> constexpr std::_Require<std::__not_<std::__is_tuple_like<_Tp> >, std::is_move_constructible<_Tp>, std::is_move_assignable<_Tp> > std::swap(_Tp&, _Tp&) [with _Tp = dumb_array]'
<source>:28:18:   required from here
/opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/type_traits:116:11: error: no type named 'type' in 'struct std::enable_if<false, void>'
  116 |     using __enable_if_t = typename enable_if<_Cond, _Tp>::type;
      |           ^~~~~~~~~~~~~
/opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/bits/move.h:213:5: note: candidate: 'template<class _Tp, long unsigned int _Nm> constexpr std::__enable_if_t<std::__is_swappable<_Tp>::value> std::swap(_Tp (&)[_Nm], _Tp (&)[_Nm])'
  213 |     swap(_Tp (&__a)[_Nm], _Tp (&__b)[_Nm])
      |     ^~~~
/opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/bits/move.h:213:5: note:   template argument deduction/substitution failed:
<source>:28:18: note:   mismatched types '_Tp [_Nm]' and 'dumb_array'
   28 |         std::swap(*this, rhs);
      |         ~~~~~~~~~^~~~~~~~~~~~
In file included from /opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/bits/stl_algobase.h:64,
                 from /opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/string:51,
                 from /opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/bits/locale_classes.h:40,
                 from /opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/bits/ios_base.h:41,
                 from /opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/ios:44:
/opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/bits/stl_pair.h:879:5: note: candidate: 'template<class _T1, class _T2> constexpr typename std::enable_if<std::__and_<std::__is_swappable<_T1>, std::__is_swappable<_T2> >::value>::type std::swap(pair<_T1, _T2>&, pair<_T1, _T2>&)'
  879 |     swap(pair<_T1, _T2>& __x, pair<_T1, _T2>& __y)
      |     ^~~~
/opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/bits/stl_pair.h:879:5: note:   template argument deduction/substitution failed:
<source>:28:18: note:   'dumb_array' is not derived from 'std::pair<_T1, _T2>'
   28 |         std::swap(*this, rhs);
      |         ~~~~~~~~~^~~~~~~~~~~~
/opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/bits/stl_pair.h:896:5: note: candidate: 'template<class _T1, class _T2> typename std::enable_if<(! std::__and_<std::__is_swappable<_T1>, std::__is_swappable<_T2> >::value)>::type std::swap(pair<_T1, _T2>&, pair<_T1, _T2>&)' (deleted)
  896 |     swap(pair<_T1, _T2>&, pair<_T1, _T2>&) = delete;
      |     ^~~~
/opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/bits/stl_pair.h:896:5: note:   template argument deduction/substitution failed:
<source>:28:18: note:   'dumb_array' is not derived from 'std::pair<_T1, _T2>'
   28 |         std::swap(*this, rhs);
      |         ~~~~~~~~~^~~~~~~~~~~~
In file included from /opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/string:54:
/opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/bits/basic_string.h:3988:5: note: candidate: 'template<class _CharT, class _Traits, class _Alloc> constexpr void std::swap(__cxx11::basic_string<_CharT, _Traits, _Allocator>&, __cxx11::basic_string<_CharT, _Traits, _Allocator>&)'
 3988 |     swap(basic_string<_CharT, _Traits, _Alloc>& __lhs,
      |     ^~~~
/opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/bits/basic_string.h:3988:5: note:   template argument deduction/substitution failed:
<source>:28:18: note:   'dumb_array' is not derived from 'std::__cxx11::basic_string<_CharT, _Traits, _Allocator>'
   28 |         std::swap(*this, rhs);
      |         ~~~~~~~~~^~~~~~~~~~~~
In file included from /opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/bits/uses_allocator_args.h:38,
                 from /opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/bits/memory_resource.h:41,
                 from /opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/string:58:
/opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/tuple:2169:5: note: candidate: 'template<class ... _Elements> constexpr typename std::enable_if<std::__and_<std::__is_swappable<_Elements>...>::value>::type std::swap(tuple<_UTypes ...>&, tuple<_UTypes ...>&)'
 2169 |     swap(tuple<_Elements...>& __x, tuple<_Elements...>& __y)
      |     ^~~~
/opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/tuple:2169:5: note:   template argument deduction/substitution failed:
<source>:28:18: note:   'dumb_array' is not derived from 'std::tuple<_UTypes ...>'
   28 |         std::swap(*this, rhs);
      |         ~~~~~~~~~^~~~~~~~~~~~
/opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/tuple:2187:5: note: candidate: 'template<class ... _Elements> constexpr typename std::enable_if<(! std::__and_<std::__is_swappable<_Elements>...>::value)>::type std::swap(tuple<_UTypes ...>&, tuple<_UTypes ...>&)' (deleted)
 2187 |     swap(tuple<_Elements...>&, tuple<_Elements...>&) = delete;
      |     ^~~~
/opt/compiler-explorer/gcc-trunk-20230709/include/c++/14.0.0/tuple:2187:5: note:   template argument deduction/substitution failed:
<source>:28:18: note:   'dumb_array' is not derived from 'std::tuple<_UTypes ...>'
   28 |         std::swap(*this, rhs);
      |         ~~~~~~~~~^~~~~~~~~~~~
Compiler returned: 1

In case of using friend swap function everything is fine.

273K
  • 29,503
  • 10
  • 41
  • 64
David Hovsepyan
  • 509
  • 1
  • 3
  • 18
  • You tried to implement copy and move operator using `std::swap()` but how is implemented `std::swap()`? It uses the move constructor (fine) but also need the move operator. And the move operator need `std::swap()` ...! – dalfaB Jul 09 '23 at 12:49
  • Don't spam C++ versions in the tags. The question is irrelevant to any C++ version. – 273K Jul 09 '23 at 15:43
  • @dalfaB Exacty, `std::swap()` uses move operations and move operations of my class are using `std::swap` again. But note, that in the latter call of `std::swap` inside move operations we're swapping `int` type and a pointer which is fine. – David Hovsepyan Jul 09 '23 at 22:44

1 Answers1

4
    friend void swap(dumb_array& lhs, dumb_array& rhs) noexcept

You defined an ordinary, plain friend function named swap() to do this task.

std::swap(*this, rhs);

But then, the shown code attempts to call std::swap. There is no std::swap overload, of course, for your class.

Changing this to swap(*this, rhs); fixes the compilation error.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • Yes, you're correct friend `swap` function fixes that and I mentioned it in the question. But I don't want to use that function, I want to use `std::swap` function and it should have worked IMO. The question is that why it doesn't work in my case? – David Hovsepyan Jul 09 '23 at 12:21
  • [related](https://stackoverflow.com/a/5695855), goes into why using `std::swap` explicitly fails, and what you should be doing (namely, `using std::swap` in the scope, and calling `swap` instead of `std::swap`). – Hasturkun Jul 09 '23 at 12:45
  • Sorry, but in my understanding these answers don't answer to my question or maybe I am missing something? – David Hovsepyan Jul 09 '23 at 12:59
  • If you wanted `std::swap` to work than you should ***specialize*** it. Declaring a `friend` function is not a specialization. Your question was unclear. – Sam Varshavchik Jul 09 '23 at 14:52
  • @SamVarshavchik I'll try to formulate my question: In my understand `std::swap` function inside copy assignment operator should have called move constructor and move assignment operator for my class, because, I assume that `std::swap` is implemented in the following way: ``` template void swap(T& lhs, T& rhs) { T temp = std::move(lhs); lhs = std::move(rhs); rhs = std::move(temp); } ``` I even could not provide `friend swap` function, but instead use `std::swap(mSize, rhs.mSize)` and `std::swap(mArray, rhs.mArray)` in move operations. – David Hovsepyan Jul 09 '23 at 19:17
  • If you agree with the above comment's assumptions when I should have not received a compile error now, but for some reason I have compile error when using `std::swap` (I don't want to use `friend swap` inside copy assignment operator, because I assumed that `std::swap` should have worked as well (as I have provided move operations for my class, i.e., there should not be any circular function call, i.e., function call to copy assignment operator). – David Hovsepyan Jul 09 '23 at 19:23
  • Your understanding is not quite correct. Your assumption is incorrect. This is not how `std::swap` is implemented. It is implemented by defining `swap()` in the `std` namespace. That's, literally, what `std::swap` means. Defining a `swap()` friend function, or class method, has absolutely no effect, whatsoever, on `std::swap`. As I already stated, `std::swap` must be ***specialized***. Do you know what template specialization is, how it works, and how to use it? – Sam Varshavchik Jul 09 '23 at 22:42
  • @SamVarshavchik OK, now I got it. Yes, I am aware a bit about template specialization. Thank you for the answers. – David Hovsepyan Jul 10 '23 at 07:53