TransWikia.com

How to split a std::string_views into a tuple-like objects using C++20 std::views::split?

Stack Overflow Asked on December 7, 2021

According to this problem, we can using C++20’sstd::views::split to split std::string_view into range of std::string_views:

std::string_view s = "this should be split into string_views";
auto views = s | std::views::split(' ')
               | std::views::transform([](auto&& rng) {
                   return std::string_view(&*rng.begin(), std::ranges::distance(rng));
                 });

If I want to store those std::string_views into some tuple like objects such as Boost.Fusion.Sequence, std::tuple, or std::array:

// magic function
auto tuple_like = to_tuple_like(views);

How can I do that? Is there any solution that no need to create intermediary like std::vector? Note that the origin s is not constexpr.

One Answer

Here is an example implementation of the "magic function" to turn your view into a tuple of string views

#include <iostream>

#include <algorithm>
#include <array>
#include <ranges>
#include <string_view>
#include <tuple>

template <std::size_t tup_size>
struct TupleType {

};

/*
must be manually defined for each size you want to support
*/
template <>
struct TupleType<6> {
    using type = std::tuple<
    std::string_view,
    std::string_view,
    std::string_view,
    std::string_view,
    std::string_view,
    std::string_view>;
};

template <>
struct TupleType<7> {
    using type = std::tuple<
    std::string_view,
    std::string_view,
    std::string_view,
    std::string_view,
    std::string_view,
    std::string_view,
    std::string_view>;
};

template <std::size_t idx, class Tup, class It>
constexpr void TupleAssignImpl(Tup& tup, It& it) {
    std::get<idx>(tup) = *it;
    ++it;
}

template <class Tup, class It>
constexpr void AssignEachImpl(Tup& tup, It& it) {
}

template <std::size_t idx, std::size_t ... Is, class Tup, class It>
constexpr void AssignEachImpl(Tup& tup, It& it) {
    TupleAssignImpl<idx>(tup, it);
    AssignEachImpl<Is...>(tup, it);
}

template <class Tup, class It, std::size_t ... Is>
constexpr void AssignEach(Tup& tup, It& it, std::index_sequence<Is...>) {
    AssignEachImpl<Is...>(tup, it);
}

template <std::size_t size, class Range>
constexpr auto ToTuple(Range const& rng) {
    auto tup = typename TupleType<size>::type{};
    auto it = std::ranges::begin(rng);
    AssignEach(tup, it, std::make_index_sequence<size>{});
    return tup;
}

int main() {
    constexpr std::string_view s = "this should be split into string_views";
    constexpr auto views = s | std::views::split(' ')
                   | std::views::transform([](auto&& rng) {
                   return std::string_view(&*rng.begin(), std::ranges::distance(rng));
                 });
    constexpr auto sz = std::distance(
        std::ranges::begin(views),
        std::ranges::end(views));
    auto tup = ToTuple<sz>(views);
    static_assert(std::is_same_v<decltype(tup),  std::tuple<
    std::string_view,
    std::string_view,
    std::string_view,
    std::string_view,
    std::string_view,
    std::string_view>>);

    std::cout << std::get<0>(tup) << std::endl;
    std::cout << std::get<1>(tup) << std::endl;
    std::cout << std::get<2>(tup) << std::endl;
    std::cout << std::get<3>(tup) << std::endl;
    std::cout << std::get<4>(tup) << std::endl;
    std::cout << std::get<5>(tup) << std::endl;
}

output:

this

should

be

split

into

string_views

a few comments:

In general there may be (probably are) better ways to achieve this. this is just my approach.

I feel it is a bit clunky to have to manually define specializations of TupleType for each size of tuple you want to support. although since the type of the tuple must strictly be known at compile time I don't know another approach.

With AssignEach i would have much rather written a simple for loop. e.g.

for (std::size_t i = 0; i < size; ++i) {
  std::get<i>(tup) = *it;
  ++it;
}

but of course the argument to std::get must strictly be known at compile time so I used AssignEach as a workaround.

the size of the tuple must be supplied as a template parameter to ToTuple since, again, in order to work we must guarantee that the tuple size is known at compile time.

as a final note: as others have pointed out, maybe the call to use std::tuple as opposed to a homogeneous range is questionable here. But you asked how it could be done, and this is an approach.

edit:

if you are okay with using std::array which is tuple-like, the approach can be much simpler. no need for the specializations of TupleType and the AssignEach workaround.

#include <iostream>

#include <algorithm>
#include <array>
#include <ranges>
#include <string_view>



template <std::size_t size, class Range>
constexpr auto ToTuple(Range const& rng) {
    auto array = std::array<std::string_view, size>{};
    std::copy(std::ranges::begin(rng), std::ranges::end(rng),
              array.begin());
    return array;
}


int main() {
    constexpr std::string_view s = "this should be split into string_views";
    constexpr auto views = s | std::views::split(' ')
                   | std::views::transform([](auto&& rng) {
                   return std::string_view(&*rng.begin(), std::ranges::distance(rng));
                 });
    constexpr auto sz = std::distance(
        std::ranges::begin(views),
        std::ranges::end(views));
        
    auto tup = ToTuple<sz>(views);

    for (const auto str : tup) {
        std::cout << str << std::endl;
    }
}

note: output is the same as the tuple example

Answered by UnforeseenPrincess on December 7, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP