boostboost-foreach

Cannot iterate on a non-copyable container returned by a function


I'm not sure of the title, because I'm not sure the issue comes from the "copyablility" of my container. I tryied quite everything but I can't get rid of this error.

Here is a simplified version of my code (please do not challenge the class design, I really would like to keep the end-used syntax in the BOOST_FOREACH):

template <typename T>
class MyContainer
{
public:
    typedef typename std::vector<T>::iterator iterator;
    typedef typename std::vector<T>::const_iterator const_iterator;

    MyContainer(std::vector<T>& vec, boost::mutex& mutex) :
        m_vector(vec),
        m_lock(mutex)
    {
    }

    iterator begin() { return m_vector.begin(); }
    const_iterator begin() const { return m_vector.begin(); }
    iterator end() { return m_vector.end(); }
    const_iterator end() const { return m_vector.end(); }


private:
    std::vector<T>& m_vector;
    boost::lock_guard<boost::mutex> m_lock;
};

template <typename T>
struct GetContainer
{
    GetContainer(std::vector<T>& vec, boost::mutex& mutex) :
        m_vector(vec),
        m_mutex(mutex)
    {
    }

    MyContainer<T> Get()
    {
        return MyContainer<T>(m_vector, m_mutex);
    }

    std::vector<T>& m_vector;
    boost::mutex& m_mutex;
};



int main()
{
    std::vector<int> v;
    v.push_back(1);
    v.push_back(2);
    boost::mutex m;

    GetContainer<int> getter(v, m);

    BOOST_FOREACH(int i, getter.Get())
    {
        std::cout << i << std::endl;
    }

    return 0;
}

The compiler complains about not having a copy constructor for MyContainer::MyContainer(const MyContainer&). I also have : error: no matching function for call to ‘MyContainer::MyContainer(boost::foreach_detail_::rvalue_probe >::value_type)’

I follow the extensibility tips: http://www.boost.org/doc/libs/1_58_0/doc/html/foreach/extensibility.html#foreach.extensibility.making__literal_boost_foreach__literal__work_with_non_copyable_sequence_types

But, making

MyContainer<T> : private boost::noncopyable

doesn't solve the issue. Nor defining the function

boost_foreach_is_noncopyable

or specializing the template struct

is_noncopyable

for MyContainer (in fact, how would I specialize this template for a template type ?)

Last "tip": If I remove the mutex and the lock from everywhere (I just pass the vector to GetContainer and to MyContainer), it works. But it doesn't work if I make

MyContainer<T> : private boost::noncopyable

(I expected it should, so I'm not sure my problem is with BOOST_FOREACH, but maybe because I return a copy of MyContainer with my getter ?)

I thank you if you read me until here, and thanks in advance for help.


Solution

  • Seems to be a limitation of BOOST_FOREACH with move-only types. I didn't find a way around it¹ (except for the - ugly - obvious approach to put the lock_guard in a shared_ptr).

    You didn't specify a c++03 requirement, though, so you can make it work without BOOST_FOREACH by replacing lock_guard with unique_lock.

    Here's my take on things in c++11 (note how generic it is):

    Live On Coliru

    #include <boost/thread.hpp>
    #include <boost/range.hpp>
    
    namespace detail {
        template <typename R, typename M>
        struct RangeLock {
            RangeLock(R&r, M& m) : _r(r), _l(m) {}
            RangeLock(RangeLock&&) = default;
    
            using iterator = typename boost::range_iterator<R>::type;
            iterator begin() { using std::begin; return begin(_r); }
            iterator end  () { using std::end;   return end  (_r); }
    
            using const_iterator = typename boost::range_iterator<R const>::type;
            const_iterator begin() const { using std::begin; return begin(_r); }
            const_iterator end  () const { using std::end;   return end  (_r); }
    
         private:
            R& _r;
            boost::unique_lock<M> _l;
        };
    }
    
    template <typename R, typename M>
        detail::RangeLock<R,M> make_range_lock(R& r, M& mx) { return {r,mx}; }
    template <typename R, typename M>
        detail::RangeLock<R const,M> make_range_lock(R const& r, M& mx) { return {r,mx}; }
    
    #include <vector>
    #include <map>
    
    int main() {
    
        boost::mutex mx;
    
        std::vector<int> const vec { 1, 2 };
        std::map<int, std::string> const map { { 1, "one" }, { 2, "two" } };
    
        for(int i : make_range_lock(vec, mx))
            std::cout << i << std::endl;
    
        for(auto& p : make_range_lock(map, mx))
            std::cout << p.second << std::endl;
    
        for(auto& p : make_range_lock(boost::make_iterator_range(map.equal_range(1)), mx))
            std::cout << p.second << std::endl;
    
    }
    

    Prints

    1
    2
    one
    two
    one
    

    ¹ not even using all the approaches from Using BOOST_FOREACH with a constant intrusive list