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

3
根据这个问题,我们可以使用C++20的std::views::splitstd::string_view分割成一系列std::string_view范围:
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


5
你必须要把字符串存储在元组中吗? - Nicol Bolas
2
一个元组是一个不同类型元素的容器,为什么要用它来存储同类型元素?其次,元组的大小在编译时确定,而你的标记数量在运行时才知道。 - bolov
1
@NicolBolas 在某些情况下,我需要解析那些 std::string_view(例如 "1"、"0.34"、"hello"),并使用 boost::fusion 反射技巧(即 BOOST_FUSION_ADAPT_STRUCT)将不同类型的值存储到一些用户定义的 struct 中。 - 康桓瑋
1
我的理解是boost::fusion可以将std::array或类似的聚合作为其过程的输入。 - Nicol Bolas
@Nicol Bolas,好的,看来我需要改变我的问题。谢谢你的建议。 - 康桓瑋
1个回答

2
这是一个将视图转换为字符串视图元组的“魔法函数”的示例实现。
#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,则方法可以简单得多。不需要特化TupleType和AssignEach的解决方法。
#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;
    }
}

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

2
为什么不使用类似元组的 std::array,而不是 std::tuple 呢?参见:https://en.cppreference.com/w/cpp/container/array/tuple_element - Caleth
说实话我不太确定。因为问题要求使用元组,所以我用了元组(: 编辑:哦,我看到它说“类似于元组”,而不仅仅是元组。也许我会更新为数组方法(我认为会更简单)。 - UnforeseenPrincess
是的,std::array方法要简单得多。我已经编辑了我的答案以包括它。谢谢你的建议! - UnforeseenPrincess
必须手动定义每个要支持的大小” 这看起来像是 C++98 的解决方案。现在肯定有比那更好的元编程技术。 - Nicol Bolas
明白。在这种情况下,我不确定是否可能获得类似元组的结构。至少我不知道方法。 - UnforeseenPrincess
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接