Concat два строковых литерала const char

Можно ли объединить два строковых литерала, используя constexpr? Или по-другому, можно ли исключить макросы в коде, например:

#define nl(str) str "\n"
int main()
{
std::cout <<
nl("usage: foo")
nl("print a message")
;

return 0;
}

Обновить: Нет ничего плохого в использовании "\n"Однако я хотел бы знать, можно ли использовать constexpr заменить эти типы макросов.

11

Решение

  1. Да, вполне возможно создавать константы времени компиляции и манипулировать ими с помощью функций constexpr и даже операторов. Тем не мение,

  2. Компилятору не требуется выполнять постоянную инициализацию любого объекта, кроме статических и потоковых объектов. В частности, временные объекты (которые не являются переменными и имеют нечто меньшее, чем продолжительность автоматического хранения) не обязательно должны быть инициализированы константой, и, насколько я знаю, ни один компилятор не делает этого для массивов. См. 3.6.2 / 2-3, которые определяют постоянную инициализацию, и 6.7.4 для более подробной формулировки относительно статических переменных длительности на уровне блоков. Ни один из них не относится к временным, чье время жизни определено в 12.2 / 3 и далее.

Таким образом, вы можете достичь желаемой конкатенации во время компиляции с помощью:

static const auto conc = <some clever constexpr thingy>;
std::cout << conc;

но вы не можете заставить его работать с:

std::cout <<  <some clever constexpr thingy>;

Обновить:

Но ты Можно заставить его работать с:

std::cout << *[]()-> const {
static constexpr auto s = /* constexpr call */;
return &s;}()
<< " some more text";

Но пунктуация шаблона слишком уродлива, чтобы сделать ее ничем иным, как интересным небольшим взломом.


(Отказ от ответственности: IANALL, хотя иногда мне нравится играть один в Интернете. Так что могут быть некоторые пыльные углы стандарта, которые противоречат вышеупомянутому.)

(Несмотря на заявление об отказе от ответственности, которое подтолкнуло @DyP, я добавил еще несколько цитат из юристов.)

1

Другие решения

Немного constexpr, посыпанный некоторым TMP и долей индексов дает мне это:

#include <array>

template<unsigned... Is> struct seq{};
template<unsigned N, unsigned... Is>
struct gen_seq : gen_seq<N-1, N-1, Is...>{};
template<unsigned... Is>
struct gen_seq<0, Is...> : seq<Is...>{};

template<unsigned N1, unsigned... I1, unsigned N2, unsigned... I2>
constexpr std::array<char const, N1+N2-1> concat(char const (&a1)[N1], char const (&a2)[N2], seq<I1...>, seq<I2...>){
return {{ a1[I1]..., a2[I2]... }};
}

template<unsigned N1, unsigned N2>
constexpr std::array<char const, N1+N2-1> concat(char const (&a1)[N1], char const (&a2)[N2]){
return concat(a1, a2, gen_seq<N1-1>{}, gen_seq<N2>{});
}

Живой пример.

Я бы уточнил это еще немного, но я должен начать и хотел бросить это до этого. Вы должны быть в состоянии работать с этим.

13

На первый взгляд, определяемые пользователем строковые литералы C ++ 11 кажутся гораздо более простым подходом. (Если, например, вы ищете способ глобально включить и отключить ввод новой строки во время компиляции)

1

  • Вы не можете вернуть (простой) массив из функции.
  • Вы не можете создать новый const char[n] внутри constexpr (§7.1.5 / 3 dcl.constexpr).
  • адресное выражение должен ссылаться на объект статической длительности хранения (§5.19 / 3 expr.const) — это запрещает некоторые трюки с объектами типов, имеющими constexpr ctor, собирающий массив для конкатенации, и ваша функция constexpr просто преобразовывает его в ptr.
  • Аргументы, передаваемые в constexpr, не считаются константами во время компиляции, поэтому вы также можете использовать fct во время выполнения — это запрещает некоторые приемы с метапрограммированием шаблона.
  • Вы не можете получить одиночные символы строкового литерала, передаваемого в функцию в качестве аргументов шаблона — это запрещает некоторые другие приемы метапрограммирования шаблона.

Итак (насколько я знаю), вы не можете получить constexpr, который возвращает char const* недавно построенной строки или char const[n], Обратите внимание, что большинство из этих ограничений не распространяется на std::array как указал Xeo.

И даже если бы вы могли вернуть некоторые char const*, возвращаемое значение не является литералом, и только смежные строковые литералы объединяются. Это происходит на этапе 6 трансляции (§2.2), который я бы назвал фазой предварительной обработки. Constexpr оцениваются позже (ref?). (f(x) f(y) где f это функция синтаксическая ошибка афаик)

Но вы можете вернуть из вашего constexpr fct объект другого типа (с constexpr ctor или агрегатом), который содержит обе строки и может быть вставлен / распечатан в basic_ostream,


Изменить: вот пример. Это довольно долго o.O
Обратите внимание, что вы можете упростить это для того, чтобы получить дополнительный символ «\ n», добавляющий конец строки. (Это более общий подход, который я только что записал по памяти.)

Edit2: На самом деле вы не можете оптимизировать его. Создание arr Элемент данных в качестве «массива const char_type» с включенным в него символом \ n (вместо массива строковых литералов) использует какой-то причудливый вариационный шаблон кода, который на самом деле немного длиннее (но он работает, см. ответ Xeo).

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

#include <cstddef>
#include <iostream>
#include <iterator>

template < typename T_Char, std::size_t t_len >
struct ct_string_vector
{
using char_type = T_Char;
using stringl_type = char_type const*;

private:
stringl_type arr[t_len];

public:
template < typename... TP >
constexpr ct_string_vector(TP... pp)
: arr{pp...}
{}

constexpr std::size_t length()
{  return t_len;  }

template < typename T_Traits >
friend
std::basic_ostream < char_type, T_Traits >&
operator <<(std::basic_ostream < char_type, T_Traits >& o,
ct_string_vector const& p)
{
std::copy( std::begin(p.arr), std::end(p.arr),
std::ostream_iterator<stringl_type>(o) );
return o;
}
};

template < typename T_String >
using get_char_type =
typename std::remove_const <
typename std::remove_pointer <
typename std::remove_reference <
typename std::remove_extent <
T_String
> :: type > :: type > :: type > :: type;

template < typename T_String, typename... TP >
constexpr
ct_string_vector < get_char_type<T_String>, 1+sizeof...(TP) >
make_ct_string_vector( T_String p, TP... pp )
{
// can add an "\n" at the end of the {...}
// but then have to change to 2+sizeof above
return {p, pp...};
}

// better version of adding an '\n':
template < typename T_String, typename... TP >
constexpr auto
add_newline( T_String p, TP... pp )
-> decltype( make_ct_string_vector(p, pp..., "\n") )
{
return make_ct_string_vector(p, pp..., "\n");
}

int main()
{
// ??? (still confused about requirements of constant init, sry)
static constexpr auto assembled = make_ct_string_vector("hello ", "world");
enum{ dummy = assembled.length() }; // enforce compile-time evaluation
std::cout << assembled << std::endl;
std::cout << add_newline("first line") << "second line" << std::endl;
}
1

Нет, для constexpr Во-первых, вам нужна легальная функция, а функции не могут вставлять и т. д. строковые литеральные аргументы.

Если вы подумаете об эквивалентном выражении в обычной функции, это будет выделение памяти и объединение строк — определенно не поддается constexpr,

0