Search code examples
c++c++11boostboost-range

Using Boost adaptors with std::bind expressions


I have the following code:

#include <boost/range/adaptor/transformed.hpp>
#include <boost/range/algorithm.hpp>

#include <iostream>
#include <functional>
#include <memory>

struct A {
    A() = default;
    A(const A&) = delete;
    A& operator=(const A&) = delete;
};

struct B {
    B() = default;
    B(const B&) = delete;
    B& operator=(const B&) = delete;

    int foo(const A&, int b)  {
        return -b;
    }
};

int main() {
    A a;
    auto b = std::make_shared<B>();
    std::vector<int> values{1, 2, 3, 2};

    using std::placeholders::_1;
    auto fun = std::bind(&B::foo, b.get(), std::ref(a), _1);
    int min = *boost::min_element(values | boost::adaptors::transformed(fun));
    std::cout << min << std::endl;
}

When I try to compile it, clang gives the following error message (full output here):

/usr/local/include/boost/optional/optional.hpp:674:80: error: object of type 'std::_Bind<std::_Mem_fn<int (Base::*)(const A &, int)> (Base *, std::reference_wrapper<A>, std::_Placeholder<1>)>' cannot be assigned because its copy assignment operator is implicitly deleted

It seems that while the bind object has a copy constructor, its copy assignment operator is deleted. I get the same error if I try to use a lambda instead of bind.

  1. Is this a bug in the C++11 standard, the libstdc++ implementation, or the Boost adaptor implementation?

  2. What is the best workaround for this? I can wrap it into an std::function. It seems that boost::bind also works. Which is the more efficient, or does it really matter?


Solution

  • Here's the problem:

    1. The standard doesn't require std::bind's return value to be copy assignable; only move constructible (and copy constructible if all bound objects are also copy constructible). For lambdas, their copy assignment operators are required to be deleted.

    2. The range adaptor actually uses transform_iterator, so the function object gets stored in the iterator.

    3. Iterators must be copy assignable, min_element tries to do that, and your program blows up.

    With the proliferation of lambdas in C++11, I'd call this a problem with the boost library, which was not designed with copy-constructible-but-not-copy-assignable function objects in mind.

    I'd actually suggest wrapping the resulting function object in a reference_wrapper:

    int min = *boost::min_element(values | boost::adaptors::transformed(std::ref(fun)));
    

    This also saves the cost of making extra copies of the functor whenever the iterator is copied.

    In the two options you listed, boost::bind should be more efficient because it doesn't have to do type erasure.