简体   繁体   English

在编译时截断字符串

[英]Truncate a String at Compile-Time

I have a string literal with a value that is out of my control (for example a #define in a config.h file) and I want to initialize a global fixed-size character array with it. 我有一个字符串文字,其值超出了我的控制范围(例如config.h文件中的#define ),我想用它初始化一个全局固定大小的字符数组。 If the string is too long, I want it to be truncated. 如果字符串太长,我希望将其截断。

Basically, what I want to achieve is the effect of 基本上,我要达到的效果是

#define SOMETEXT "lorem ipsum"
#define LIMIT 8

char text[LIMIT + 1];
std::strncpy(text, SOMETEXT, LIMIT);
text[LIMIT] = '\0';

except that I cannot use this code because I want text to be a statically initialized constexpr . 除了不能使用此代码,因为我希望text成为静态初始化的constexpr

How can I do this? 我怎样才能做到这一点?

Note: I have already found a solution to this problem but since a search on Stack Overflow did not yield me a satisfactory result (though many helpful hints for similar problems), I wanted to share my solution. 注意:我已经找到了解决此问题的方法,但是由于在Stack Overflow上的搜索无法获得令人满意的结果(尽管有许多有关类似问题的有用提示),所以我想分享我的解决方案。 If you have a better (more elegant) solution, please show it nevertheless. 如果您有更好(更优雅)的解决方案,请继续进行展示。 I will accept the most elegant answer in one week. 我将在一个星期内接受最优雅的答案。

The first step in solving this problem is formalizing it. 解决此问题的第一步是将其形式化。 Given a string (character sequence) 给定一个字符串(字符序列)

s = s 0 , …, s m s = s 0 ,…, s m

with s i = 0 if and only if i = m for i = 0, …, m and m ∈ ℕ and a number n ∈ ℕ, we want to obtain another string (character sequence) S I = 0,当且仅当对于i = 0,...,mm∈ℕ和数量为n∈ℕI = ,我们想要获得另一个字符串(字符序列)

t = t 0 , …, t n t = t 0 ,…, t n

with

  • t i = 0 if i = n , 如果i = n ,则t i = 0,
  • t i = s i if i < m and T I = S i如果 <M
  • t i = 0 otherwise t i = 0否则

for i = 0, …, n . 对于i = 0,…, n

Next, realize that the length of a string ( m in the above formalization) is readily computed at compile-time: 接下来,意识到在编译时很容易计算出字符串的长度(上述形式中的m ):

template <typename CharT>
constexpr auto
strlen_c(const CharT *const string) noexcept
{
  auto count = static_cast<std::size_t>(0);
  for (auto s = string; *s; ++s)
    ++count;
  return count;
}

I'm making use of C++14 features like return type deduction and generalized constexpr functions here. 我在这里使用C ++ 14功能,例如返回类型推导和广义constexpr函数。

Now the function that, given i ∈ 0, …, n , computes t i is also straight-forward. 现在,给出的函数I∈0,...,N,计算T I也是直接的。

template <typename CharT>
constexpr auto
char_at(const CharT *const string, const std::size_t i) noexcept
{
  return (strlen_c(string) > i) ? string[i] : static_cast<CharT>(0);
}

If we know n ahead of time, we can use this to put together a first quick-and-dirty solution: 如果我们提前知道n ,我们可以使用它来组合第一个快捷的解决方案:

constexpr char text[] = {
  char_at(SOMETEXT, 0), char_at(SOMETEXT, 1),
  char_at(SOMETEXT, 2), char_at(SOMETEXT, 3),
  char_at(SOMETEXT, 4), char_at(SOMETEXT, 5),
  char_at(SOMETEXT, 6), char_at(SOMETEXT, 7),
  '\0'
};

It compiles and initializes text with the desired values, but that's about all good that can be said about it. 它使用所需的值编译和初​​始化text ,但这可以说是一件很不错的事情。 The fact that the length of the string is needlessly computed over and over again in each call to char_at is probably the least concern. 在每次对char_at调用中都不必要一遍又一遍地计算字符串的长度这一事实可能是最不用担心的。 What is more problematic is that the solution (as ugly as it already is) clearly grows totally unwieldy if n approaches larger values and that the constant n is implicitly hard-coded. 更大的问题是,如果n接近更大的值,则解决方案(虽然已经很丑)显然变得完全笨拙,并且常数n被隐式地硬编码。 Don't even consider using tricks like 甚至不要考虑使用诸如

constexpr char text[LIMIT] = {
#if LIMIT > 0
  char_at(SOMETEXT, 0),
#endif
#if LIMIT > 1
  char_at(SOMETEXT, 1),
#endif
#if LIMIT > 2
  char_at(SOMETEXT, 2),
#endif
  // ...
#if LIMIT > N
#  error "LIMIT > N"
#endif
  '\0'
};

to work around this limitation. 解决此限制。 The Boost.Preprocessor library might help cleaning this mess up somewhat but it's not worth the thing. Boost.Preprocessor库可能会帮助清理这些混乱,但这是不值得的。 There is a much cleaner solution using template meta-programming waiting around the corner. 有一个使用模板元编程的清洁得多的解决方案,指日可待。

Let's see how we can write a function that returns the properly initialized array at compile-time. 让我们看看如何编写一个在编译时返回正确初始化的数组的函数。 Since a function cannot return an array, we need to wrap it in a struct but as it turns out, std::array already does this (and more) for us, so we'll use it. 由于函数无法返回数组,因此我们需要将其包装在struct但事实证明, std::array已经为我们完成了(以及更多)操作,因此我们将使用它。

I define a template helper struct with a static function help that returns the desired std::array . 我定义了一个带有static函数help的模板帮助器struct ,该函数返回所需的std::array Besides from the character type parameter CharT , this struct is templated on the length N to which to truncate the string (equivalent to n in the above formalization) and the number M of characters we have already added (this has nothing to do with the variable m in the above formalization). 除了字符类型参数CharT ,此struct还以截断字符串的长度N (相当于上述形式化中的n )和我们已经添加的字符数M (与变量无关)为模板m以上的形式化)。

template <std::size_t N, std::size_t M, typename CharT>
struct truncation_helper
{
  template <typename... CharTs>
  static constexpr auto
  help(const CharT *const string,
       const std::size_t length,
       const CharTs... chars) noexcept
  {
    static_assert(sizeof...(chars) == M, "wrong instantiation");
    const auto c = (length > M) ? string[M] : static_cast<CharT>(0);
    return truncation_helper<N, M + 1, CharT>::help(string, length, chars..., c);
  }
};

As you can see, truncation_helper::help recursively calls itself popping one character off the front of the to-be-truncated string as it goes. 如您所见, truncation_helper::help递归调用自己,从而在要截断的字符串的前面弹出一个字符。 I'm passing the length of the string around as an additional parameter to avoid it having to be re-computed in each recursive call anew. 我将字符串的长度作为附加参数传递,以避免必须在每个递归调用中重新计算字符串。

We terminate the process as M reaches N by providing this partial specialization. 通过提供这种局部专业化,我们在M达到N终止该过程。 This is also the reason why I need the struct because function templates cannot be partially specialized. 这也是我之所以需要struct原因,因为功能模板不能部分地专门化。

template <std::size_t N, typename CharT>
struct truncation_helper<N, N, CharT>
{
  template <typename... CharTs>
  static constexpr auto
  help(const CharT *,       // ignored
       const std::size_t,   // ignored
       const CharTs... chars) noexcept
  {
    static_assert(sizeof...(chars) == N, "wrong instantiation");
    return truncation_helper::workaround(chars..., static_cast<CharT>(0));
  }

  template <typename... CharTs>
  static constexpr auto
  workaround(const CharTs... chars) noexcept
  {
    static_assert(sizeof...(chars) == N + 1, "wrong instantiation");
    std::array<CharT, N + 1> result = { chars... };
    return result;
  }
};

The terminating invocation of help does not use the string and length parameters but has to accept them nevertheless for compatibility. help的终止调用不使用stringlength参数,但是为了兼容性必须接受它们。

For reasons I don't understand, I cannot use 由于我不明白的原因,我无法使用

std::array<CharT, N + 1> result = { chars..., 0 };
return result;

but rather have to call the workaround helper-helper function. 但必须调用workaround helper-helper函数。

What smells a little about this solution is that I need the static_assert ions to make sure the correct instantiation is called and that my solution introduces all those CharTs... type parameters when we actually already know that the type must be CharT for all of the chars... parameters. 该解决方案的一点气味是,我需要static_assert离子以确保调用了正确的实例化,并且当我们实际上已经知道所有类型的类型必须为CharT时,我的解决方案才引入了所有这些CharTs...类型参数。 chars...参数。

Putting it all together, we get the following solution. 综上所述,我们得到以下解决方案。

#include <array>
#include <cstddef>

namespace my
{

  namespace detail
  {

    template <typename CharT>
    constexpr auto
    strlen_c(const CharT *const string) noexcept
    {
      auto count = static_cast<std::size_t>(0);
      for (auto s = string; *s; ++s)
        ++count;
      return count;
    }

    template <std::size_t N, std::size_t M, typename CharT>
    struct truncation_helper
    {
      template <typename... CharTs>
      static constexpr auto
      help(const CharT *const string, const std::size_t length, const CharTs... chars) noexcept
      {
        static_assert(sizeof...(chars) == M, "wrong instantiation");
        const auto c = (length > M) ? string[M] : static_cast<CharT>(0);
        return truncation_helper<N, M + 1, CharT>::help(string, length, chars..., c);
      }
    };

    template <std::size_t N, typename CharT>
    struct truncation_helper<N, N, CharT>
    {
      template <typename... CharTs>
      static constexpr auto
      help(const CharT *, const std::size_t, const CharTs... chars) noexcept
      {
        static_assert(sizeof...(chars) == N, "wrong instantiation");
        return truncation_helper::workaround(chars..., static_cast<CharT>(0));
      }

      template <typename... CharTs>
      static constexpr auto
      workaround(const CharTs... chars) noexcept
      {
        static_assert(sizeof...(chars) == N + 1, "wrong instantiation");
        std::array<CharT, N + 1> result = { chars... };
        return result;
      }
    };

  }  // namespace detail

  template <std::size_t N, typename CharT>
  constexpr auto
  truncate(const CharT *const string) noexcept
  {
    const auto length = detail::strlen_c(string);
    return detail::truncation_helper<N, 0, CharT>::help(string, length);
  }

}  // namespace my

It can then be used like this: 然后可以这样使用:

#include <cstdio>
#include <cstring>

#include "my_truncate.hxx"  // suppose we've put above code in this file

#ifndef SOMETEXT
#  define SOMETEXT "example"
#endif

namespace /* anonymous */
{
  constexpr auto limit = static_cast<std::size_t>(8);
  constexpr auto text = my::truncate<limit>(SOMETEXT);
}

int
main()
{
  std::printf("text = \"%s\"\n", text.data());
  std::printf("len(text) = %lu <= %lu\n", std::strlen(text.data()), limit);
}

Acknowledgments This solution was inspired by the following answer: c++11: Create 0 to N constexpr array in c++ 致谢此解决方案的灵感来自以下答案: c ++ 11:在c ++中创建0到N的constexpr数组

An alternative to create the std::array : 另一种创建std::array

namespace detail
{
    template <typename C, std::size_t N, std::size_t...Is>
    constexpr std::array<C, sizeof...(Is) + 1> truncate(const C(&s)[N], std::index_sequence<Is...>)
    {
        return {(Is < N ? s[Is] : static_cast<C>(0))..., static_cast<C>(0)};
    }

}

template <std::size_t L, typename C, std::size_t N>
constexpr std::array<C, L + 1> truncate(const C(&s)[N])
{
    return detail::truncate(s, std::make_index_sequence<L>{});
}

Demo 演示版

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM