С++ concat два `const char` строковые литералы

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

#define nl(str) str "\n"

int main()
{
  std::cout <<
      nl("usage: foo")
      nl("print a message")
      ;

  return 0;
}

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

Ответ 1

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

  • Компилятор не обязан выполнять постоянную инициализацию любого объекта, кроме объектов статичности и продолжительности. В частности, временные объекты (которые не являются переменными и имеют что-то меньшее, чем продолжительность автоматического хранения) не требуются для постоянной инициализации, и насколько я знаю, компилятор не делает это для массивов. См. 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>;

Update:

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

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

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


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

(Несмотря на отказ от ответственности и нажал @DyP, я добавил еще несколько ссылок на язык-адвокат.)

Ответ 2

Немного 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>{});
}

Пример в реальном времени.

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

Ответ 3

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

Ответ 4

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

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

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

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


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

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

Примечание: в качестве 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;
}

Ответ 5

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

Если вы думаете об эквивалентном выражении в регулярной функции, оно будет выделять память и конкатенировать строки - определенно не поддающиеся constexpr.