Первым шагом в решении этой проблемы является ее формализация.Учитывая, строка (последовательность символов)
сек = сек , & hellip ;, сек м
с с я = 0, если и только если i = m для i = 0, & hellip ;, m и m ∈ & # x2115; и ряд п ∈ & # x2115 ;, мы хотим получить другую строку (последовательность символов)
т = т , & hellip ;, т п
с
- т я = 0, если я = п,
- т я = с я, если я < м и
- t я = 0 в противном случае
для я = 0, & hellip ;, п.
Далее, понимают, что длина строки ( м в приведенном выше формализации) легко вычисляется во время компиляции:
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, & hellip ;, п, вычисляет T я также прямо вперед.
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);
}
Если мы знаем п раньше времени, мы можем использовать это, чтобы собрать первый быстрый и грязный раствор:
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++
Это довольно круто, определенно более элегантно, чем у меня. Я бы добавил только static_cast (0) 'чтобы отключить многочисленные (безобидные) предупреждения. Вы знаете, почему ваш' return {(/ * param pack expr * /) ..., 0}; 'правильно инициализирует массив так, как должен, но мне пришлось использовать' Обходное решение?? –
5gon12eder
@ 5gon12eder: Я не понимаю, зачем вам нужна «обходная» часть, она также работает для вашего кода без [здесь] (http://ideone.com/um0IWd). – Jarod42
Потому что первый 'text = «« Не совсем то, что я ожидаю для »примера« 'усекается до 8 символов. – 5gon12eder