根据这个问题,我们可以使用C++20的std::views::split
将std::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.Sequence
、std::tuple
或std::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
,那么该方法可以简单得多。无需对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;
}
}
注意:输出与元组示例相同