在编译时截断字符串

时间:2015-07-29 20:41:27

标签: c++ arrays char c++14 constexpr

我有一个字符串文字,其值不受我的控制(例如#define文件中的config.h),我想用它初始化一个全局固定大小的字符数组。如果字符串太长,我希望它被截断。

基本上,我想要实现的是

的效果
#define SOMETEXT "lorem ipsum"
#define LIMIT 8

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

除了我无法使用此代码,因为我希望text成为静态初始化的constexpr

我该怎么做?

  

注意:我已经找到了解决这个问题的方法,但由于对Stack Overflow的搜索没有给我一个满意的结果(虽然许多有用的类似问题的提示),我想分享我的解。如果您有更好(更优雅)的解决方案,请尽情展示。我将在一周内接受最优雅的答案。

2 个答案:

答案 0 :(得分:2)

解决这个问题的第一步是将其正式化。给定一个字符串(字符序列)

s = s 0 ,..., s m

i = m 时, 对于 i = 0,..., m m ∈ℕ和数字 n ∈ℕ,我们想要获取另一个字符串(字符序列)

t = t 0 ,..., t n

  • t i = 0 if i = n
  • t i = s i if i < m
  • t i = 0否则

for i = 0,..., n

接下来,意识到在编译时很容易计算字符串的长度( 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;
}

我正在使用C ++ 14等功能,例如返回类型演绎和广义constexpr函数。

现在给定 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);
}

如果我们提前知道 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'
};

它使用所需的值编译和初​​始化text,但这可以说是关于它的所有好处。在每次调用char_at时,一遍又一遍地不必要地计算字符串的长度这一事实可能是最不重要的。更有问题的是,如果 n 接近更大的值并且常数 n 被隐式地硬编码,那么解决方案(如同它本来的那样丑陋)显然变得非常笨重。甚至不要考虑使用像

这样的技巧
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'
};

解决此限制。 Boost.Preprocessor库可能有助于清理这个混乱,但这不值得。有一个更清晰的解决方案,使用模板元编程等待即将到来。

让我们看看如何编写一个在编译时返回正确初始化数组的函数。由于函数不能返回数组,我们需要将它包装在struct中,但事实证明,std::array已经为我们做了这个(以及更多),所以我们将使用它。

我定义了一个模板帮助器struct,其中static函数help返回了所需的std::array。除了字符类型参数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);
  }
};

正如您所看到的,truncation_helper::help以递归方式调用自身在要截断的字符串前面弹出一个字符。我将字符串的长度作为附加参数传递,以避免必须在每次递归调用中重新计算它。

我们通过提供此部分特化来终止M到达N的过程。这也是我需要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;
  }
};

help的终止调用不使用stringlength参数,但为了兼容性,必须接受它们。

由于我不明白的原因,我不能使用

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

而是必须调用workaround帮助程序辅助函数。

关于这个解决方案的一点点气味是我需要static_assert离子以确保调用正确的实例化,并且当我们实际已经知道时,我的解决方案引入了所有这些CharTs...类型参数对于所有CharT参数,类型必须为chars...

总而言之,我们得到以下解决方案。

#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

然后可以像这样使用:

#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);
}

致谢此解决方案的灵感来自以下答案:c++11: Create 0 to N constexpr array in c++

答案 1 :(得分:2)

创建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