3

According to this problem, we can using C++20's std::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.

double-beep
  • 5,031
  • 17
  • 33
  • 41
康桓瑋
  • 33,481
  • 5
  • 40
  • 90
  • 5
    Do you *have* to store the strings in a `tuple`? – Nicol Bolas Jul 23 '20 at 02:42
  • 2
    A tuple is a heterogeneous container why would you use it for homogeneous elements? Second, a tuple size is set at compile time, while your number of tokens is known at runtime. – bolov Jul 23 '20 at 07:08
  • 1
    @NicolBolas In some case, I need to parse those `std::string_view`s (maybe "1", "0.34", "hello") and store the values of different types to some user-defined `struct` using `boost::fusion` reflection tricks (i.e., `BOOST_FUSION_ADAPT_STRUCT`). – 康桓瑋 Jul 23 '20 at 16:45
  • 1
    @康桓瑋: My understanding is that `boost::fusion` can take `std::array`s or similar aggregates as inputs to its processes. – Nicol Bolas Jul 23 '20 at 16:47
  • @Nicol Bolas, Ok then seems I need to change my questions. Thanks for your advice. – 康桓瑋 Jul 23 '20 at 16:52

1 Answers1

2

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.

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

double-beep
  • 5,031
  • 17
  • 33
  • 41
  • 2
    Rather than `std::tuple`, why not `std::array`, which is similarly [tuple-like](https://en.cppreference.com/w/cpp/container/array/tuple_element) – Caleth Jul 23 '20 at 15:52
  • honestly not sure. i did tuple because the question asked for tuple (: edit: oh i see it says "tuple like" not just tuple. maybe i will update with an array approach (which i think would be simpler) – UnforeseenPrincess Jul 23 '20 at 15:54
  • yes the std::array approach is much simpler. ive edited my answer to include that. thank you for the suggestion! – UnforeseenPrincess Jul 23 '20 at 16:03
  • "*must be manually defined for each size you want to support*" That looks like a C++98 solution. There have to be better metaprogramming techniques than that these days. – Nicol Bolas Jul 23 '20 at 16:05
  • @NicolBolas feel free to suggest one. as i said i am not aware of one. note: i have added an approach that uses std::array that doesnt not require the specializations – UnforeseenPrincess Jul 23 '20 at 16:08
  • I like your solution so I upvote, but, in reality, the origin std::string_view is usually not `constexpr`. – 康桓瑋 Jul 23 '20 at 17:03
  • understood. in that case i am not sure it is possible to obtain a tuple like structure. at least i would not know the way. – UnforeseenPrincess Jul 23 '20 at 17:06