Using Boost adaptors with C++11 lambdas
Asked Answered
A

5

21

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?

Argentite answered 8/8, 2012 at 20:22 Comment(1)
I think you need 1.51. At least that is what made it work for me.Bertolde
T
10

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.

Transoceanic answered 8/8, 2012 at 20:38 Comment(1)
std::function works with type erasure, i.e., with a virtual function. This additional cost is unnecessary here and probably unacceptable. (also inlining is hindered) My solution would be using std::ref.Basiliabasilian
R
11

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;
Reflux answered 8/5, 2015 at 21:33 Comment(0)
T
10

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.

Transoceanic answered 8/8, 2012 at 20:38 Comment(1)
std::function works with type erasure, i.e., with a virtual function. This additional cost is unnecessary here and probably unacceptable. (also inlining is hindered) My solution would be using std::ref.Basiliabasilian
C
6

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; })));
Catullus answered 9/8, 2012 at 21:5 Comment(0)
G
0

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; })));
Garbo answered 30/7, 2018 at 16:16 Comment(0)
B
0

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.

Basiliabasilian answered 2/11, 2023 at 13:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.