c++ - Constexpr: Convert a list of string views into a list of char arrays
问题描述
I came across an interesting constexpr problem that haven't been able to fully crack. The last piece of the puzzle that I'm missing is the following
// Given a constexpr array of string views
constexpr std::array<std::string_view, X> views = ...;
// Convert it to a list of char arrays
constexpr std::tuple<std::array<char, Xs>...> buffers = ...;
My problem here is finding the right size for each array. How can I extract the sizes of the string_views in views
and pass them as template parameters to another function?
I could just use the same size for every buffer, big enough to hold every argument, but I'm wondering if there is a way of optimizing their sizes since the info is known at compile time.
Full description of the problem I'm trying to solve. (in case someone can come up with a better approach, also cause I think it's interesting...)
I want to create a macro that converts a list of arguments into a list of name-value pairs. For instance
// Adding types explicitly to show what I want to achieve.
int x;
float y;
double z;
using named_values_t = std::tuple<
std::pair<const char*, int&>,
std::pair<const char*, float&>,
std::pair<const char*, double&>>;
named_values_t nv = MY_MACRO(x, y, z);
The const char*
adds significant difficulty but it's a requirement for a third-party lib.
Now I know that can be done with Boost.Preprocessor but I'd like to do it just using the STL and constexpr methods to avoid adding boost just for this. Also I know this would be trivial on a compiler with support for constexpr std::string
but I'm using C++17.
The string processing can be easily done with constexpr functions,
// Split "one, two, three" into {"one", "two", "three"}.
template <size_t Count>
constexpr std::array<std::string_view, Count> split_arguments(std::string_view);
However, I cannot pass these string_views directly as char pointers, since at this point they're just pointers to a bigger array in memory (the complete "one, two, three"
). To pass as const char*
each element needs to be null-terminated.
But we can build an std::array
for each std::string_view
and copy its contents, this way we have a char array for each argument name, that will generate a null-terminated segment of memory for each name.
constexpr std::string_view args = "one, two, three";
constexpr std::array<std::string_view, 3> views = split_arguments<3>(args); // {"one", "two", "three"}
constexpr std::tuple<std::array<char, Xs>...> buffers = make_buffers<Xs...>(views);
Here I'm not able to figure out how pass the lengths of the views as template arguments to the next function.
Working solution here (using bigger fixed-sized buffers): https://gcc.godbolt.org/z/WKsbvb
The fixed-size buffer solution is ok, but it would be great to go that extra step to fit the buffers to their actual size.
解决方案
只要views
是具有静态存储持续时间的变量(而不是由 constexpr 函数调用创建的纯右值),您就可以使用通常的auto&
模板参数和std::index_sequence
技巧来实现它:
#include<array>
#include<string_view>
#include<tuple>
#include<utility>
#include<type_traits>
#include<cstddef>
namespace detail {
template<std::size_t N>
constexpr auto copy_string(std::string_view s) {
std::array<char,N+1> ret{}; // zero-initialize
for(std::size_t i=N;i--;) ret[i]=s[i];
return ret;
}
template<auto &V,std::size_t ...II>
constexpr auto buffers(std::index_sequence<II...>) {
return std::make_tuple(copy_string<V[II].size()>(V[II])...);
}
}
template<auto &V> constexpr auto buffers=
detail::buffers<V>
(std::make_index_sequence
<std::tuple_size_v<std::remove_reference_t<decltype(V)>>>());
constexpr std::array<std::string_view, 3> views = {"C","++",""};
static_assert(std::is_same_v
<decltype(buffers<views>),
const std::tuple<std::array<char,2>,
std::array<char,3>,
std::array<char,1>>>);
static_assert(std::get<0>(buffers<views>)[0]=='C');
static_assert(std::get<1>(buffers<views>)[1]=='+');
static_assert(std::get<2>(buffers<views>)[0]=='\0');
推荐阅读
- html - 不同的 HTML 文件链接到一个具有完全相同样式标题的 CSS 文件,但在网页上显示不同的样式
- javascript - javascript 代码不能作为用户脚本工作
- python - 没有使用运行时错误设置secret_key,但我很确定我正确设置了密钥
- laravel-dusk - 如何点击使用 laravel 黄昏隐藏的元素?
- python - 如何在 Python 3.8 中的一行中读取由空格分隔的多个输入?
- lisp - 在 Common Lisp 中传递对象引用
- node.js - 如何使用 Google Cloud Function 启动和停止 vm 实例?
- javascript - 如何使用 Javascript 获取新打开的选项卡的 url?
- html - 在 CSS 文件中加载本地 woff2 文件不起作用
- c# - 条件为假时执行 IF 语句 C#