c++boostcompiler-errorsboost-rangeboost-iterators

How to convert a single object to a boost::any_range?


I'm trying to create and return a boost:any_range that contains only one object (I don't know if that's the core problem) but I get the following errors:

Below you can find the relevant code snippets:

  1. That's the function I want to call and which fails during compiling:
const HandleRange BasicCollection::GetPartHandles() const
{
    return HandleRange
    //return -> also tried this
    {
        Handle(GenericHandleManager::CreatePartHandleValue(GenericHandleManager::GetPartIdx(_collectionHandle)))
    };
}

Somehow this works, but that's not really clean:

const HandleRange BasicCollection::GetPartHandles() const
{
    auto container = { _collectionHandle };

    return container | boost::adaptors::transformed([collectionHandle = _collectionHandle](const auto & index_value)
    {
        return HandleRange::value_type
        {
            Handle(GenericHandleManager::CreatePartHandleValue(GenericHandleManager::GetPartIdx(collectionHandle)))
        };
    });
}
  1. That's the HandleRange type that shall be returned:
/**
 * Defines an alias representing a boost range for handles.
 */
 using HandleRange = boost::any_range<Handle, boost::forward_traversal_tag, const Handle>;
  1. The used Handle object:
class Handle
{
public:
     /**
      * Construct a handle from a handle value.
      * @param    value   The handle's value.
      */
      inline explicit Handle(int_fast64_t value) noexcept : _value(value)
      {
      }
...
}

Thanks for any suggestions!


Solution

  • Somehow this works, but that's not really clean

    That should not compile without diagnostics, as auto is deduced as std::initializer_list<Handle>.

    That approach invokes Undefined Behaviour because the initializer list doesn't exist after returning.

    Solutions

    The any_range should be able to return an iterator range.

    Pointers are iterators.

    Any single object o can be seen as a range [&o, &o + 1). That's a valid iterator range.

    Combining these would already be a solution if GenericHandleManager::CreatePartHandleValue(...) returns a reference:

    const HandleRange BasicCollection::GetPartHandles() const {
        Handle& h =
          GenericHandleManager::CreatePartHandleValue(
              GenericHandleManager::GetPartIdx(_collectionHandle));
        return boost::make_iterator_range(&h, &h + 1));
    }
    

    Singleton Ranges

    If it returns a temporary, though, you'll need to make that "a range":

    template <typename T>
    struct SingletonRange : boost::iterator_range<T*> {
        T val;
        SingletonRange(T val)
          : boost::iterator_range<T*>(std::addressof(val), std::addressof(val) + 1),
            val(std::move(val))
        { }
    };
    

    Now you can safely¹ write (even though CreatePartHandleValue returns a temporary):

    HandleRange BasicCollection::GetPartHandles() const {
        Handle h =
            GenericHandleManager::CreatePartHandleValue(
                GenericHandleManager::GetPartIdx(_collectionHandle));
    
        return SingletonRange<Handle> {h};
    }
    

    Full Demo

    Live On Coliru

    #include <boost/range/iterator_range.hpp>
    
    template <typename T>
    struct SingletonRange : boost::iterator_range<T*> {
        T val;
        SingletonRange(T val)
          : boost::iterator_range<T*>(std::addressof(val), std::addressof(val) + 1),
            val(std::move(val))
        { }
    };
    
    struct Handle{};
    struct GenericHandleManager {
        static int GetPartIdx(Handle)            { return 42; }
        static Handle CreatePartHandleValue(int) { return {}; }
    };
    
    #include <boost/range/any_range.hpp>
    using HandleRange = boost::any_range<Handle, boost::forward_traversal_tag, const Handle>;
    
    struct BasicCollection {
        HandleRange GetPartHandles() const;
      private:
        Handle _collectionHandle;
    };
    
    HandleRange BasicCollection::GetPartHandles() const {
        Handle h =
            GenericHandleManager::CreatePartHandleValue(
                GenericHandleManager::GetPartIdx(_collectionHandle));
    
        return SingletonRange<Handle> {h};
    }
    
    #include <iostream>
    int main() {
        BasicCollection coll;
        for (Handle h : coll.GetPartHandles()) {
            std::cout << "Handle in loop\n";
    
            boost::ignore_unused_variable_warning(h);
        }
    }
    

    Keep in mind that copying that range is as expensive as copying an iterator_range<Handle*> plus copying the Handle itself. I'm assuming the Handle is lightweight (as usual for handles)

    Prints

    Handle in loop
    

    ¹ as long as you make sure you don't use any iterators from the SingletonRange after the lifetime of the range. This is a common C++ pattern though