boost range adaptor that makes a collection

1.5k views Asked by At

I want to write a boost adaptor to put at the end of a chain of adaptors to create a collection, like this:

set<string> s = input | filtered(...) | transformed(...) | to_set;

Using Method 3.1 I wrote the code below which seems to work as expected:

namespace detail
{
    struct to_set_forwarder
    {
    };
};

template <class R> inline auto operator|(R& r, detail::to_set_forwarder)
{
    return set<R::value_type>(r.begin(), r.end());
}

template <class R> inline auto operator|(const R& r, detail::to_set_forwarder)
{
    return set<const R::value_type>(r.begin(), r.end());
}

namespace
{
    const auto to_set = detail::to_set_forwarder();
}

void F()
{
    vector<string> input{ "1", "2", "3" };
    auto result = input
        //| boost::adaptors::filtered([](const auto& _) { return true; })
        | to_set;
}

But if I uncomment that one line, I get:

error C2338: The C++ Standard forbids containers of const elements because allocator is ill-formed.

If I make the first parameter to operator| be && then that works until I uncomment the filtered() line, and then I get:

error C2825: 'R': must be a class or namespace when followed by '::'

What's the right way to do this?

1

There are 1 answers

0
sehe On

Like it says, standard containers cannot store const elements. Neither do you want to (because you store copies anyways. If you wanted them const, you can make the container const).

The fix is:

template <class R> inline auto operator|(const R& r, detail::to_set_forwarder)
{
    return std::set<typename R::value_type>(r.begin(), r.end());
}

(removing const).

Live On Coliru

#include <boost/range.hpp>
#include <boost/range/adaptors.hpp>
#include <set>
#include <vector>
#include <iostream>
#include <string>

namespace detail
{
    struct to_set_forwarder
    {
    };
}

template <class R> inline auto operator|(R& r, detail::to_set_forwarder)
{
    return std::set<typename R::value_type>(r.begin(), r.end());
}

template <class R> inline auto operator|(const R& r, detail::to_set_forwarder)
{
    return std::set<typename R::value_type>(r.begin(), r.end());
}

namespace
{
    const auto to_set = detail::to_set_forwarder();
}

void F()
{
    std::vector<std::string> input{ "1", "2", "3", "2" };
    auto result = input
        | boost::adaptors::filtered([](const auto& _) { return true; })
        | to_set;

    for (auto x : result)
        std::cout << x << " ";
}

int main() {
    F();
}

Prints

1 2 3

PS: You are missing typename qualifications (try another compiler than MSVC sometimes)

PS: You can boost::copy_range:

auto result = boost::copy_range<std::set<std::string> >(input
    | boost::adaptors::filtered([](const auto& _) { return true; })
    );