Using Boost adaptors with C++11 lambdas

6.3k Views Asked by At

I tried to compile this code:

#include <boost/range/adaptors.hpp>
#include <boost/range/algorithm.hpp>
#include <vector>

int main() {
    std::vector<int> v{
        1,5,4,2,8,5,3,7,9
    };
    std::cout << *boost::min_element(v | boost::adaptors::transformed(
            [](int i) { return -i; })) << std::endl;
    return 0;
}

The compilation failed with the following error message (after a long template instantiation novel):

/usr/local/include/boost/iterator/transform_iterator.hpp:84:26: error: use of deleted function ‘main()::<lambda(int)>::<lambda>()’
../main.cpp:12:5: error: a lambda closure type has a deleted default constructor

I googled the problem, and found this in the Boost Users mailing list archive. It suggested that using #define BOOST_RESULT_OF_USE_DECLTYPE would solve the problem. I put it into the very beginning of my code, but it still doesn't compile. The length of the error message seems to be much shorter, but the error message at the end is the same. I'm currently using Boost 1.50.

What can be the problem here? Is there any way to make this work?

5

There are 5 best solutions below

1
On BEST ANSWER

http://smellegantcode.wordpress.com/2011/10/31/linq-to-c-or-something-much-better/

But you can use this, that works well.

#include <boost/range/adaptors.hpp>
#include <boost/range/algorithm.hpp>
#include <vector>
#include <functional>

int main() {
    std::vector<int> v{
        1,5,4,2,8,5,3,7,9
    };
    std::function<int(int)> func = [](int i) { return -i; };
    std::cout << *boost::min_element(v | boost::adaptors::transformed(
    func)) << std::endl;
    return 0;
}

http://liveworkspace.org/code/b78b3f7d05049515ac207e0c12054c70

#define BOOST_RESULT_OF_USE_DECLTYPE works fine in VS2012 for example.

0
On

As others have already mentioned, the problem is, that std::min_element() (which is simply wrapped by boost::min_element()) wants to copy the begin iterator of the range. But this iterator contains an instance of the transforming callable (same thing happens with a filtering callable) which therefore must be copied as well. But lambdas are not copyable so this will not compile.

It was suggested to wrap the lambda in a std::function, but this has the downside of a virtual function call with every invocation. Virtual function calls are not for free and you'll have to pay that cost for every element in your range. (virtual functions also hinder inlining)

The better solution is to use std::cref() so that the iterator contains a std::reference_wrapper which is basically a pointer:

static constexpr auto transformer = [](int i) { return -i; };
std::cout << *boost::min_element(v | boost::adaptors::transformed(std::cref(transformer))) << std::endl;

See it working on Coliru.

This also works for capturing lambdas, however those cannot be static constexpr.

0
On

This is covered at http://boost.2283326.n4.nabble.com/range-cannot-use-lambda-predicate-in-adaptor-with-certain-algorithms-td3560157.html and https://svn.boost.org/trac/boost/ticket/4189 - the problem is that some algorithms expect to be able to copy-construct (and default-construct, and copy-assign) their predicate, which can't be done with a lambda.

The workaround is to wrap the lambda in a std::function:

*boost::min_element(
    v | boost::adaptors::transformed(std::function<int(int)>(
        [](int i) { return -i; })));

I've asked (at Inferring the call signature of a lambda or arbitrary callable for "make_function") for a way to write a make_function such that one can just write:

*boost::min_element(
    v | boost::adaptors::transformed(make_function(
        [](int i) { return -i; })));
0
On

With C++17 feature class template argument deduction you can wrap with std::function simplier, like this:

*boost::min_element(
    v | boost::adaptors::transformed(std::function(
        [](int i) { return -i; })));
0
On

You can turn a non-capturing lambda into a function pointer by putting a "+" in front of it.

std::vector<int> v{1,5,4,2,8,5,3,7,9};
std::cout << *boost::min_element(v | 
    boost::adaptors::transformed(+[](int i) 
    {
        return -i; 
    })) << std::endl;