Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can you swap a std::queue with a lambda comparator?

I am trying to clear a std::queue using the example in https://stackoverflow.com/a/709161/837451 via a swap. However, it doesn't seem to work with a lambda comparator due to the "deleted function" error.

Minimal working failing example:

#include <queue>
#include <vector>
using namespace std;
int main(){
    typedef pair<int,float> ifpair;
    auto comp = []( ifpair a,  ifpair b ) { return a.second > b.second; };
    typedef priority_queue< ifpair , vector<ifpair>, decltype( comp ) > t_npq;
    t_npq npq( comp );
    //do something with npq. finish using it (without emptying it) and clear for next round
    t_npq empty( comp );
    swap(npq , empty);
}

Compile with

g++ -std=c++11 /tmp/test.cpp -o /tmp/o

And I get the following error:

/usr/include/c++/4.8/bits/move.h:176:11: error: use of deleted function ‘main()::__lambda0& main()::__lambda0::operator=(const main()::__lambda0&)’
   __a = _GLIBCXX_MOVE(__b);
       ^
/tmp/test.cpp:6:18: note: a lambda closure type has a deleted copy assignment operator

g++ -v

Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/4.8/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu/Linaro 4.8.1-10ubuntu9' --with-bugurl=file:///usr/share/doc/gcc-4.8/README.Bugs --enable-languages=c,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.8 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.8 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-4.8-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-objc-gc --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.8.1 (Ubuntu/Linaro 4.8.1-10ubuntu9) 

I'm kind of curious what exactly is going on here but more importantly I would really like to know how to make this work.

like image 509
mmdanziger Avatar asked Dec 23 '13 20:12

mmdanziger


Video Answer


3 Answers

While the result of a lambda expression is move constructible it isn't necessarily move assignable and certainly not copyable. I would just bypass the problem by using a std::reference_wrapper<decltype(comp)> for the comparator object:

typedef pair<int,float> ifpair;
auto comp = []( ifpair a,  ifpair b ) { return a.second > b.second; };
typedef priority_queue< ifpair , vector<ifpair>,
                        std::reference_wrapper<decltype( comp ) >> t_npq;
t_npq npq( std::ref(comp) );
t_npq empty( std::ref(comp) );
swap(npq , empty);

Since the full type information of the lambda expression is retained by the reference wrapper, this should work even if the closure isn't empty and, where viable, it should be possible to inline the function.

like image 81
Dietmar Kühl Avatar answered Oct 12 '22 11:10

Dietmar Kühl


As the compile error indicates, lambda objects aren't assignable. You can use a different type of functor for the queue but still write it as a labmda:

  1. Use std::function<bool(ifpair,ifpair)>: http://ideone.com/HZywoV

    But this adds (probably noticable) overhead due to some more indirections in the implementation of std::function but I guess this heavily depends on the implementation of the standard library and the compiler optimizations. Might be the nicest solution regarding how the code looks though.

  2. Use a function pointer bool(*)(ifpair,ifpair): http://ideone.com/ZhFq3C

    This should not suffer from any overhead compared to std::function but to your current solution, since there might be some compiler optimizations done to your lambda code which are then not possible (i.e. inlining it into the rest of the std::queue code which for example eliminates copying the two pairs). Using function pointers looks pretty old-school though.

  3. Use a custom functor class which can be as simple as: http://ideone.com/9pcQFc

    template<typename Pair>
    struct GreaterBySecond {
        bool operator()(Pair a, Pair b) const {
            return a.second > b.second;
        }
    };
    

    This should eliminate all overhead discussed above. I'd prefer this if performance matters.

like image 2
leemes Avatar answered Oct 12 '22 10:10

leemes


Have you tried to use std::function?

#include <queue>
#include <vector>
#include <functional>
using namespace std;
int main(){
    typedef pair<int,float> ifpair;
    std::function< bool ( ifpair, ifpair )> comp = []( ifpair a,  ifpair b ) { return a.second > b.second; };
    typedef priority_queue< ifpair , vector<ifpair>, decltype( comp ) > t_npq;
    t_npq npq( comp );
    //do something with npq. finish using it (without emptying it) and clear for next round
    t_npq empty( comp );
    swap(npq , empty);
}
like image 1
y3i12 Avatar answered Oct 12 '22 11:10

y3i12