首页 > 解决方案 > 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.

标签: c++c++17constexpr

解决方案


只要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');

推荐阅读