2015-07-29 2 views
1

У меня есть строковый литерал со значением, которое находится вне моего контроля (например, #define в файле config.h), и я хочу инициализировать с ним глобальный массив символов фиксированного размера. Если строка слишком длинная, я хочу, чтобы она была усечена.Обрезать строку во время компиляции

В общем, что я хочу, чтобы достичь является эффект

#define SOMETEXT "lorem ipsum" 
#define LIMIT 8 

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

за исключением того, что я не могу использовать этот код, потому что я хочу text быть статически инициализируется constexpr.

Как это сделать?

Примечание: я уже нашел решение этой проблемы, но поскольку поиск на переполнение стека не дала мне удовлетворительный результат (хотя много полезных советов для подобных задач), я хотел бы поделиться своим решением. Если у вас есть лучшее (более элегантное) решение, пожалуйста, покажите его. Я приму самый элегантный ответ за неделю.

ответ

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

+0

Это довольно круто, определенно более элегантно, чем у меня. Я бы добавил только static_cast (0) 'чтобы отключить многочисленные (безобидные) предупреждения. Вы знаете, почему ваш' return {(/ * param pack expr * /) ..., 0}; 'правильно инициализирует массив так, как должен, но мне пришлось использовать' Обходное решение?? – 5gon12eder

+0

@ 5gon12eder: Я не понимаю, зачем вам нужна «обходная» часть, она также работает для вашего кода без [здесь] (http://ideone.com/um0IWd). – Jarod42

+0

Потому что первый 'text = «« Не совсем то, что я ожидаю для »примера« 'усекается до 8 символов. – 5gon12eder

2

Первым шагом в решении этой проблемы является ее формализация.Учитывая, строка (последовательность символов)

сек = сек , & 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 с функцией statichelp, который возвращает желаемый 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

'auto count = static_cast (0);' Серьезно? –

+0

@LightnessRacesinOrbit Когда я впервые увидел это в «Эффективном современном C++», это была моя реакция. Но я должен признать, что я все больше привык к этому. – 5gon12eder

+0

Это совершенно абсурдно. И грустно. :-( –

Смежные вопросы