Объедините две постоянные строки (или массива) в одну постоянную строку (или массив) во время компиляции - PullRequest
6 голосов
/ 01 июля 2010

В C # и Java можно создавать константные строки, используя одну или несколько других константных строк.Я пытаюсь достичь того же результата в C ++ (точнее, в C ++ 0x, если быть точным), но не знаю, какой синтаксис я бы использовал для его достижения, если такое возможно в C ++.Вот пример, иллюстрирующий то, что я хочу сделать:

#include <stdio.h>

const char array1[] = "Hello ";
const char array2[] = "world!\n";
const char array3[] = array1 + array2; // C++ doesn't like it when I try this

int main() {

    printf(array3);

    return 0;

}

Какие-нибудь указатели?(Не каламбур).

РЕДАКТИРОВАТЬ: Мне нужно иметь возможность применить это также к целочисленным массивам - не только к массивам символов.Однако в обоих случаях объединяемые массивы будут фиксированного размера и будут константами времени компиляции.

Ответы [ 5 ]

7 голосов
/ 01 июля 2010

Итак ...

Вы не хотите выполнять конкатенацию во время выполнения.

Вы не хотите использовать препроцессор.

Вы хотите работатьс константами и выходными константами.

ОК.Но вам это не понравится:

#include <boost/mpl/string.hpp>

#include <iostream>

int main()
{
  using namespace boost::mpl;

  typedef string<'Hell', 'o '> hello;
  typedef string<'Worl', 'd!'> world;
  typedef insert_range<hello, end<hello>::type, world>::type hello_world;

  std::cout << c_str<hello_world>::value << std::endl;

  std::cin.get();
}
5 голосов
/ 31 мая 2012

Я взглянул на твой вопрос и заметил, что никто не ответил на него.Мое отклонение, чтобы ответить, заняло целый день, чтобы довести его до совершенства, но мне все равно это было нужно для моей работы.

Моей работе нужно constexpr, и я написал ответ с учетом этого.Ваша работа не определяет это, но это не может повредить.

Что не сработало;то есть моя первая попытка

(я использую GCC-4.7 от MacPorts на 10-летнем PowerPC Mac.)

Вы можете легко включить (C ++ 11) variadicСписок параметров функции для любого типа кортежа:

template < typename Destination, typename ...Source >
constexpr
auto  initialize( Source&& ...args ) -> Destination
{ return Destination{ce_forward<Source>(args)...}; }

(Шаблон функции ce_forward аналогичен std::forward, за исключением того, что я явно сделал его constexpr.)

(КогдаЯ не поместил Destination в тело, мой компилятор дал мне ошибки, связанные с невозможностью инициализации с помощью std::initialization_list, поэтому форма, которую я сейчас имею, должна работать с любым агрегатом или конструктором из типа назначенияподдерживает.)

Но сначала нам нужно пойти другим путем, а затем использовать мой код выше для перевода обратно.Я попробовал код, подобный следующему:

template < typename Destination, typename Source, typename Size1, typename Size2, typename ...Args >
constexpr
auto  fill_from_array( Source&& source, Size1 index_begin, Size2 index_end, Args&& ...args )
 -> Destination
{
    return ( index_begin < index_end )
      ? fill_from_array<Destination>( ce_forward<Source>(source), index_begin + 1, index_end, ce_forward<Args>(args)..., ce_forward<Source>(source)[index_begin] )
      : initialize<Destination>( ce_forward<Args>(args)... );
}

(Так как мне также потребовалось два источника, я сделал большую версию этой функции.)

Когда я фактически запустил этот код, мой компьютер вырубилсячерез час после превышения виртуальной памяти.Я предполагаю, что это вызывает бесконечный цикл или что-то в этом роде.(Или, может быть, это конечно, но слишком много для моей древней системы.)

Моя вторая попытка

Я рыскал ТАК, пока не нашел материал, который мог бы пригодиться:

и нашел решение из тех, что я прочитал: Разбор строк во время компиляции - Часть I .По сути, мы используем вариант моего initialize шаблона функции выше;вместо того, чтобы основывать инициализаторы исключительно на параметрических параметрических параметрах, мы используем отображение из вариабельных параметров шаблона.

Самый простой источник отображения - неотрицательные целые числа:

#include <cstddef>

template < std::size_t ...Indices >
struct index_tuple
{ using next = index_tuple<Indices..., sizeof...(Indices)>; };

template < std::size_t Size >
struct build_indices
{ using type = typename build_indices<Size - 1>::type::next; };

template < >
struct build_indices< 0 >
{ using type = index_tuple<>; };

Шаблон класса index_tupleэто то, что мы будем передавать для отображения, в то время как шаблон класса build_indices помещает экземпляры index_tuple в правильный формат:

index_tuple<>
index_tuple<0>
index_tuple<0, 1>
index_tuple<0, 1, 2>
...

Мы создаем index_tuple объекты с шаблоном функции:

template < std::size_t Size >
constexpr
auto  make_indices() noexcept -> typename build_indices<Size>::type
{ return {}; }

Мы используем указанные index_tuple объекты для их вклада в заголовок шаблона шаблона функции:

#include <array>

template < std::size_t N, std::size_t M, std::size_t ...Indices >
constexpr
std::array<char, N + M - 1u>
fuse_strings_impl( const char (&f)[N], const char (&s)[M], index_tuple<Indices...> );

Третий параметр не получает имя, потому что нам не понадобитсясам объект.Нам просто нужно "std :: size_t ... Indices" в заголовке.Мы знаем, что при расширении превратится в «0, 1, ..., X».Мы передадим это упорядоченное расширение в вызов функции, который будет расширен до необходимых инициализаторов.В качестве примера, давайте посмотрим на определение функции выше:

template < std::size_t N, std::size_t M, std::size_t ...Indices >
constexpr
std::array<char, N + M - 1u>
fuse_strings_impl( const char (&f)[N], const char (&s)[M], index_tuple<Indices...> )
{ return {{ get_strchr<Indices>(f, s)... }}; }

Мы будем возвращать array с первым элементом как get_strchr<0>(f,s), вторым как get_strchr<1>(f,s), и такна.Обратите внимание, что это имя функции оканчивается на «_impl», потому что я скрываю использование index_tuple и гарантирую правильный базовый случай, вызывая публичную версию:

template < std::size_t N, std::size_t M >
constexpr
std::array<char, N + M - 1u>
fuse_strings( const char (&f)[N], const char (&s)[M] )
{ return fuse_strings_impl(f, s, make_indices<N + M - 2>()); }

И вы можете попытаться кодировать так:

#include <iostream>
#include <ostream>

int  main()
{
    using std::cout;
    using std::endl;

    constexpr auto  initialize_test = initialize<std::array<char, 15>>( 'G',
     'o', 'o', 'd', 'b', 'y', 'e', ',', ' ', 'm', 'o', 'o', 'n', '!', '\0' );
    constexpr char  hello_str[] = "Hello ";
    constexpr char  world_str[] = "world!";
    constexpr auto  hw = fuse_strings( hello_str, world_str );

    cout << initialize_test.data() << endl;
    cout << hw.data() << endl;
}

Есть некоторые тонкости, за которыми нужно следить.

  • Ваши декларации const(expr) char str[] = "Whatever"; должны использовать [] вместо *, поэтому компилятор распознает ваш объект каквстроенный массив, а не как указатель на неизвестную (фиксированную) память времени выполнения.
  • Поскольку встроенные массивы нельзя использовать в качестве возвращаемых типов, вы должны использовать std::array какзамена.Проблема в том, когда вам нужно использовать результат для последующего слияния.У объекта std::array должен быть общедоступный элемент нестатических данных соответствующего встроенного типа массива, но имя этого элемента не указано в стандарте и, вероятно, не соответствует.Таким образом, вы должны создать std::array аналог, чтобы официально избежать хаков.
  • Нельзя использовать один и тот же код для общих объединений массивов и строковых объединений.Строка - это массив char, где последний элемент должен быть '\0'.Эти значения NUL должны быть пропущены при чтении из строк, но добавлены при записи.Вот почему нечетные 1 и 2 появляются в моем коде.Для общих объединений массивов (включая нестроковые char единицы) каждый элемент в каждом массиве должен читаться, и никакие дополнительные элементы не должны добавляться в объединенный массив.из get_strchr:
    template < std::size_t N >
    constexpr
    char  ce_strchr( std::size_t i, const char (&s)[N] )
    {
        static_assert( N, "empty string" );
        return (i < ( N - 1 )) ? s[i] : throw "too big";
    }
    
    template < std::size_t N, std::size_t M, std::size_t ...L >
    constexpr
    char  ce_strchr( std::size_t i, const char (&f)[N], const char (&s)[M], const char (&...t)[L] )
    {
        static_assert( N, "empty string" );
        return (i < ( N - 1 )) ? f[i] : ce_strchr(i + 1 - N, s, t...);
    }
    
    template < std::size_t I, std::size_t N, std::size_t ...M >
    constexpr
    char  get_strchr( const char (&f)[N], const char (&...s)[M] )
    { return ce_strchr(I, f, s...); }
    

    (надеюсь, вы прочтете это).

5 голосов
/ 01 июля 2010

Использовать строковый объект:

#include <iostream>
#include <string>

const std::string s1 = "Hello ";
const std::string s2 = "world!\n";
const std::string s3 = s1 + s2;

int main()
{
  std::cout << s3 << std::endl;
}
4 голосов
/ 01 июля 2010

В C ++ 0x вы можете сделать следующее:

template<class Container>
Container add(Container const & v1, Container const & v2){
   Container retval;
   std::copy(v1.begin(),v1.end(),std::back_inserter(retval));
   std::copy(v2.begin(),v2.end(),std::back_inserter(retval));
   return retval;
}

const std::vector<int> v1 = {1,2,3};
const std::vector<int> v2 = {4,5,6};
const std::vector<int> v3 = add(v1,v2);

Я не думаю, что есть какой-либо способ сделать это для контейнеров STL в C ++ 98 (часть добавления для v3вы можете сделать, но вы не можете использовать списки инициализаторов для v1 и v2 в C ++ 98), и я не думаю, что есть какой-либо способ сделать это для необработанных массивов в C ++ 0x или C++ 98.

4 голосов
/ 01 июля 2010

В подобных случаях часто пригодится препроцессор

#define ARRAY1 "Hello "
#define ARRAY2 "world!\n"

const char array1[] = ARRAY1;
const char array2[] = ARRAY2;
const char array3[] = ARRAY1 ARRAY2;

Примечание: нет + необходимо.

...