swap std::unique_ptr with lambda as deleter -- GCC

c++, c++11, gcc, lambda, unique-ptr

Solution

To expand on Jonathan Wakely's answer:

When you swap to `unique_ptr`s, you also have to swap their deleters. The problem you are seeing boils down to this: clang can swap two lambdas of the same type, gcc cannot (and the standard allows both as Jonathan quotes it). Demonstration:

#include <utility>

int main() {
  auto f = [](){};
  auto g(f);
  std::swap(f, g);
}

This code works with clang but fails to compile with gcc. (And that is OK.)

That is why it is happening.

I suggest the following:

#include <memory>
#include <utility>

struct addrinfo { };

void freeaddrinfo(addrinfo* ) { }

struct deleter {
  void operator()(struct addrinfo* ptr) {
    if (ptr != nullptr)
      freeaddrinfo(ptr);
  }
};

using resources_keeper = std::unique_ptr<struct addrinfo, deleter>;

int main() {

    resources_keeper plouf1(nullptr);
    resources_keeper plouf2(nullptr);

    std::swap(plouf1, plouf2);
    return 0;
}

Note that the code became cleaner and more readable as well.

If you absolutely have to solve this with lambdas, then perhaps you could try something hackish like this: Swap only the pointers but not the deleters.

#include <iostream>
#include <memory>
#include <utility>

using namespace std;

template <class T, class D>
void swap_pointers_but_not_deleters(unique_ptr<T,D>& x, unique_ptr<T,D>& y) noexcept {

  T* x_ptr = x.release();

  x.reset(y.release());

  y.reset(x_ptr);
}

int main() {

  auto deleter = [](int* p){ delete p; };

  unique_ptr<int,decltype(deleter)> a(new int(1),deleter);

  unique_ptr<int,decltype(deleter)> b(new int(2),deleter);

  swap_pointers_but_not_deleters(a, b);

  cout << "a = " << *a << ", b = " << *b << endl;
}

Although this code seems to work, I really don't like it. I suggest the first solution that does not use lambdas.

Problem

Can we use a lambda as a deleter with a std::unique_ptr ? Actualy, I did it with clang++ and it was happy to do so. I'm using `std::swap` to swap to `std::unique_ptr<ObjType, decltyp(deleter)>;` where `auto deleter = [](struct addrinfo* ptr){if (ptr != nullptr) {freeaddrinfo(ptr);} };`. Clang's swap seems to do not need a copy assignment operator, but gcc's std::swap did, as you can see in those logs : ``` In file included from /usr/include/c++/4.8.1/memory:81:0, from /home/zenol/proj/src/PROJ/TCPClient.cpp:28: /usr/include/c++/4.8.1/bits/unique_ptr.h: In instantiation of ‘std::unique_ptr<_Tp, _Dp>& std::unique_ptr<_Tp, _Dp>::operator=(std::unique_ptr<_Tp, _Dp>&&) [with _Tp = addrinfo; _Dp = Proj::TCPClient::connect(const Proj::SocketAddress&, int)::__lambda0]’: /usr/include/c++/4.8.1/bits/move.h:176:11: required from ‘void std::swap(_Tp&, _Tp&) [with _Tp = std::unique_ptr<addrinfo, Proj::TCPClient::connect(const Proj::SocketAddress&, int)::__lambda0>]’ /home/zenol/proj/src/Proj/SocketHelp.hpp:109:50: required from ‘void Proj::retrieve_addresses(std::string, int, addrinfo&, addrinfo*&, T&, U) [with T = std::unique_ptr<addrinfo, Proj::TCPClient::connect(const Proj::SocketAddress&, int)::__lambda0>; U = Proj::TCPClient::connect(const Proj::SocketAddress&, int)::__lambda0; std::string = std::basic_string<char>]’ /home/zenol/proj/src/PROJ/TCPClient.cpp:65:49: required from here /usr/include/c++/4.8.1/bits/unique_ptr.h:193:16: erreur: use of deleted function ‘Proj::TCPClient::connect(const Proj::SocketAddress&, int)::__lambda0& Proj::TCPClient::connect(const Proj::SocketAddress&, int)::__lambda0::operator=(const Proj::TCPClient::connect(const Proj::SocketAddress&, int)::__lambda0&)’ get_deleter() = std::forward<deleter_type>(__u.get_deleter()); ^ /home/zenol/proj/src/Proj/TCPClient.cpp:56:21: note: a lambda closure type has a deleted copy assignment operator auto deleter = [](struct addrinfo* ptr) ^ ``` What says the standard? Can I manage to wap those two std::unique_ptr ? Are they a workaround ? (Maybe encapsulating the lambda inside a std::function? ...) Edit : Here is a small example that should be more or less the same thing : ``` auto deleter = [](struct addrinfo* ptr) {if (ptr != nullptr) {freeaddrinfo(ptr);} }; std::unique_ptr<struct addrinfo, decltype(deleter)> resources_keeper(nullptr, deleter); int main() { decltype(resources_keeper) plouf1(nullptr, deleter); decltype(resources_keeper) plouf2(nullptr, deleter); std::swap(plouf1, plouf2); return 0; } ``` The error : ``` In file included from /usr/include/c++/4.8.1/bits/stl_pair.h:59:0, from /usr/include/c++/4.8.1/bits/stl_algobase.h:64, from /usr/include/c++/4.8.1/memory:62, from mini.cpp:1: /usr/include/c++/4.8.1/bits/move.h: In instantiation of ‘void std::swap(_Tp&, _Tp&) [with _Tp = __lambda0]’: /usr/include/c++/4.8.1/tuple:381:36: required from ‘void std::_Tuple_impl<_Idx, _Head, _Tail ...>::_M_swap(std::_Tuple_impl<_Idx, _Head, _Tail ...>&) [with long unsigned int _Idx = 1ul; _Head = __lambda0; _Tail = {}]’ /usr/include/c++/4.8.1/tuple:382:35: required from ‘void std::_Tuple_impl<_Idx, _Head, _Tail ...>::_M_swap(std::_Tuple_impl<_Idx, _Head, _Tail ...>&) [with long unsigned int _Idx = 0ul; _Head = addrinfo*; _Tail = {__lambda0}]’ /usr/include/c++/4.8.1/tuple:667:33: required from ‘void std::tuple<_T1, _T2>::swap(std::tuple<_T1, _T2>&) [with _T1 = addrinfo*; _T2 = __lambda0]’ /usr/include/c++/4.8.1/tuple:1050:7: required from ‘void std::swap(std::tuple<_Elements ...>&, std::tuple<_Elements ...>&) [with _Elements = {addrinfo*, __lambda0}]’ /usr/include/c++/4.8.1/bits/unique_ptr.h:269:21: required from ‘void std::unique_ptr<_Tp, _Dp>::swap(std::unique_ptr<_Tp, _Dp>&) [with _Tp = addrinfo; _Dp = __lambda0]’ /usr/include/c++/4.8.1/bits/unique_ptr.h:484:7: required from ‘void std::swap(std::unique_ptr<_Tp, _Dp>&, std::unique_ptr<_Tp, _Dp>&) [with _Tp = addrinfo; _Dp = __lambda0]’ mini.cpp:21:29: required from here /usr/include/c++/4.8.1/bits/move.h:176:11: erreur: use of deleted function ‘__lambda0& __lambda0::operator=(const __lambda0&)’ __a = _GLIBCXX_MOVE(__b); ^ mini.cpp:9:17: note: a lambda closure type has a deleted copy assignment operator auto deleter = [](struct addrinfo* ptr) ^ In file included from /usr/include/c++/4.8.1/bits/stl_pair.h:59:0, from /usr/include/c++/4.8.1/bits/stl_algobase.h:64, from /usr/include/c++/4.8.1/memory:62, from mini.cpp:1: /usr/include/c++/4.8.1/bits/move.h:177:11: erreur: use of deleted function ‘__lambda0& __lambda0::operator=(const __lambda0&)’ __b = _GLIBCXX_MOVE(__tmp); ^ ```

Original source