Reputation: 2642
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);
^
Upvotes: 9
Views: 2610
Reputation: 58431
As it turns out, you can solve it with lambdas, as long as they can be converted to function pointers (lambdas capturing nothing).
#include <memory>
#include <utility>
struct addrinfo { };
void freeaddrinfo(addrinfo* ) { }
auto deleter = [](struct addrinfo* ptr) {
if (ptr != nullptr)
freeaddrinfo(ptr);
};
using resources_keeper = std::unique_ptr<struct addrinfo, void(*)(struct addrinfo*)>;
int main() {
resources_keeper plouf1(nullptr,deleter);
resources_keeper plouf2(nullptr,deleter);
std::swap(plouf1, plouf2);
return 0;
}
However, I still like my other solution with the struct better. It is likely to be the most efficient one (thanks to inlining), followed by the solution presented here. Passing a heavy-weight std::function
looks like an overkill to me if the deleter implementation is really simple. Whether these performance considerations matter, it is the profiler's job to tell.
Upvotes: 2
Reputation: 58431
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.
Upvotes: 4
Reputation: 171263
This has nothing to do with unique_ptr
or tuple
, you can reduce the error to this:
int main()
{
auto deleter = []() { };
auto del2 = deleter;
deleter = static_cast<decltype(deleter)>(del2);
}
Which compiles with Clang but fails with G++, giving this error:
t.cc: In function ‘int main()’:
t.cc:5:11: error: use of deleted function ‘main()::<lambda()>& main()::<lambda()>::operator=(const main()::<lambda()>&)’
deleter = static_cast<decltype(deleter)>(del2);
^
t.cc:3:19: note: a lambda closure type has a deleted copy assignment operator
auto deleter = []() { };
^
The last C++11 standard says in [expr.prim.lambda]/19:
The closure type associated with a lambda-expression has a deleted (8.4.3) default constructor and a deleted copy assignment operator. It has an implicitly-declared copy constructor (12.8) and may have an implicitly-declared move constructor (12.8).
So it is up to the compiler whether the type is move-assignable or not.
Upvotes: 6
Reputation: 505
I can reproduce a similar error with the following code:
struct A
{
A() = default;
A(A&&) = default;
//A & operator=(A&&) = default;
A(A const & ) = delete;
};
int main()
{
A a, b;
std::swap(a,b);
}
Uncomment the move assignment operator and the error goes away. I'm guessing gcc doesn't allow move assignment of lambas (I'm using version 4.7.2). Change the lambda to an actual function or functor and you should be alright.
Upvotes: 2