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

岁酱吖の 提交于 2021-01-28 04:56:13

问题


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:

  • error C2893: Failed to specialize function template 'range_iterator<C,void>::type boost::range_adl_barrier::begin(T &)'
  • note: With the following template arguments:
  • note: 'T=const WrappedRange'
  • error C2672: 'end': no matching overloaded function found
  • error C2893: Failed to specialize function template
    'range_iterator<C,void>::type boost::range_adl_barrier::end(T &)'
  • note: With the following template arguments: note: 'T=const
    WrappedRange'

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!


回答1:


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



来源:https://stackoverflow.com/questions/65238116/how-to-convert-a-single-object-to-a-boostany-range

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!