C++ 在编译时截断字符串

C++ 在编译时截断字符串,c++,arrays,char,c++14,constexpr,C++,Arrays,Char,C++14,Constexpr,我有一个字符串文字,其值超出了我的控制范围,例如config.h文件中的define,我想用它初始化一个全局固定大小的字符数组。如果字符串太长,我希望它被截断 基本上,我想要达到的是 #define SOMETEXT "lorem ipsum" #define LIMIT 8 char text[LIMIT + 1]; std::strncpy(text, SOMETEXT, LIMIT); text[LIMIT] = '\0'; 除了我不能使用这段代码,因为我希望文本是静态初始化的cons

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

基本上,我想要达到的是

#define SOMETEXT "lorem ipsum"
#define LIMIT 8

char text[LIMIT + 1];
std::strncpy(text, SOMETEXT, LIMIT);
text[LIMIT] = '\0';
除了我不能使用这段代码,因为我希望文本是静态初始化的constexpr

我该怎么做

注意:我已经找到了这个问题的解决方案,但是由于对堆栈溢出的搜索并没有产生令人满意的结果,尽管有许多关于类似问题的有用提示,我想分享我的解决方案。如果你有更好更优雅的解决方案,请展示给我们。我将在一周内接受最优雅的回答


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

s=s0,…,sm

对于si=0,当且仅当i=m表示i=0,…,m和m∈ ℕ 和一个数字n∈ ℕ, 我们想获得另一个字符串序列

t=t0,…,tn

如果i=n,则ti=0, 如果i 接下来,认识到在编译时容易计算上述形式化中的字符串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;
}
如果我们提前知道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'
};
它用所需的值编译和初始化文本,但这就是它的全部优点。在对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'
};
要绕过这个限制。图书馆也许能帮你清理一下这些乱七八糟的东西,但这不值得。有一个更干净的解决方案使用模板元编程等待在拐角处

让我们看看如何编写一个函数,在编译时返回正确初始化的数组。由于函数无法返回数组,我们需要将其包装在结构中,但事实证明,std::array已经为我们完成了这项工作,而且还有更多的工作,所以我们将使用它

我用一个静态函数help定义了一个模板帮助器结构,该函数返回所需的std::array。除了字符类型参数图之外,该结构是以长度N为模板的,在上述形式化中,该长度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);
  }
};
终止对帮助的调用不使用字符串和长度参数,但出于兼容性考虑,必须接受它们

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

std::array<CharT, N + 1> result = { chars..., 0 };
return result;
然后可以这样使用它:

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

确认此解决方案受以下答案启发:

解决此问题的第一步是将其形式化。给定一个字符串序列

s=s0,…,sm

对于si=0,当且仅当i=m表示i=0,…,m和m∈ ℕ 和一个数字n∈ ℕ, 我们想获得另一个字符串序列

t=t0,…,tn

如果i=n,则ti=0, 如果i 接下来,认识到在编译时容易计算上述形式化中的字符串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;
}
如果我们提前知道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'
};
它用所需的值编译和初始化文本,但这就是它的全部优点。在对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'
};
要绕过这个限制。图书馆也许能帮你清理一下这些乱七八糟的东西,但这不值得。有一个更干净的解决方案使用模板元编程等待在拐角处

让我们看看如何编写一个函数,在编译时返回正确初始化的数组。由于函数无法返回数组,我们需要将其包装在结构中,但事实证明,std::array已经为我们完成了这项工作,而且还有更多的工作,所以我们将使用它

我用一个静态函数help定义了一个模板帮助器结构,该函数返回所需的std::array。除了字符类型参数表之外,该结构是以长度N为模板的,在上述形式化中,该长度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);
  }
};
终止对帮助的调用不使用字符串和长度参数,但出于兼容性考虑,必须接受它们

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

std::array<CharT, N + 1> result = { chars..., 0 };
return result;
然后可以这样使用它:

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

确认此解决方案的灵感来自以下答案:

创建std::阵列的替代方案:


创建std::数组的替代方法:


自动计数=静态0;真的吗?当我第一次看到这是在现代C++中提出的,这是我的反应。但我必须承认我越来越习惯了。这完全是荒谬的。而且很悲伤-请注意,您可以参考字符数组模板constepr std::size_t strlen_cconst CharT&[N]noexcept{return N;}和std::index_sequences,找到Sutter的auto foo=type{bar};太冗长。自动计数=静态\u cast0;真的吗?当我第一次看到这是在现代C++中提出的,这是我的反应。但我必须承认我越来越习惯了。这完全是荒谬的。而且很悲伤-请注意,您可以参考字符数组模板constepr std::size_t strlen_cconst CharT&[N]noexcept{return N;}和std::index_sequences,找到Sutter的auto foo=type{bar};太啰嗦了。那很酷,绝对比我的优雅。我只想添加一个static_cast0来消除众多无害的警告。您知道为什么返回{/*param pack expr*/…,0};正确地初始化了数组,但我必须使用变通方法?@5gon12eder:我不明白你为什么需要变通方法部分。它也适用于没有的代码。因为第一个text=与我期望的不完全相同,例如被截断为8个字符。@5gon12eder:我没有检查输出:-/但是gcc和clang的行为不同,因此存在编译器错误或UB。。。我没能找到UB…谢谢你调查此事。我已经问过了。那很酷,肯定比我的更优雅。我只想添加一个static_cast0来消除众多无害的警告。您知道为什么返回{/*param pack expr*/…,0};正确地初始化了数组,但我必须使用变通方法?@5gon12eder:我不明白你为什么需要变通方法部分。它也适用于没有的代码。因为第一个text=与我期望的不完全相同,例如被截断为8个字符。@5gon12eder:我没有检查输出:-/但是gcc和clang的行为不同,因此存在编译器错误或UB。。。我没能找到UB…谢谢你调查此事。我已经问过了。