如何使用C++20 std::views:split将std::string_views拆分为类似元组的对象



根据这个问题,我们可以使用C++20的std::views::splitstd::string_view拆分为std::string_view的范围s:

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));
                 });

如果我想将这些std::string_view存储到一些类似元组的对象中,如Boost.Fusion.Sequencestd::tuplestd::array:

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

我该怎么做?有没有什么解决方案不需要创建像std::vector这样的中介?注意,原点s不是constexpr

下面是一个"魔术函数";将视图转换为字符串视图的元组

#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;
}

输出:

this
should
be
split
into
string_views

一些评论:

总的来说,可能有(可能有(更好的方法来实现这一点。这只是我的做法。

我觉得必须为您想要支持的每种大小的元组手动定义TupleType的专业化有点麻烦。尽管由于元组的类型必须在编译时严格已知,所以我不知道其他方法。

使用AssignEach,我更愿意编写一个简单的for循环。例如

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

当然,std::get的参数必须在编译时严格已知,所以我使用AssignEach作为一种变通方法。

元组的大小必须作为模板参数提供给ToTuple,因为同样,为了工作,我们必须保证在编译时元组大小是已知的。

最后要注意的是:正如其他人所指出的,也许使用std::tuple而不是均匀范围的呼吁在这里是值得怀疑的。但你问如何做到这一点,这是一种方法。

如果您可以使用类似元组的std::array,那么该方法可以简单得多。无需对TupleTypeAssignEach解决方法进行专门化。

#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;
    }
}

注意:输出与元组示例相同

最新更新