我有一个字符串文字,其值不受我的控制(例如#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的搜索没有给我一个满意的结果(虽然许多有用的类似问题的提示),我想分享我的解。如果您有更好(更优雅)的解决方案,请尽情展示。我将在一周内接受最优雅的答案。
答案 0 :(得分:2)
解决这个问题的第一步是将其正式化。给定一个字符串(字符序列)
s = s 0 ,..., s m 子>
当 i = m 时, 对于 i = 0,..., m 和 m ∈ℕ和数字 n ∈ℕ,我们想要获取另一个字符串(字符序列)t = t 0 ,..., t n 子>
与
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
的终止调用不使用string
和length
参数,但为了兼容性,必须接受它们。
由于我不明白的原因,我不能使用
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>{});
}