Подтвердить что ты не робот

Избегая первой строки в строчном литерале С++ 11?

Необработанные строковые литералы в С++ 11 очень приятные, за исключением того, что очевидный способ их форматирования приводит к избыточной новой строке \n в качестве первого символа.

Рассмотрим следующий пример:

    some_code();
    std::string text = R"(
This is the first line.
This is the second line.
This is the third line.
)";
    more_code();

Очевидное обходное решение кажется настолько уродливым:

    some_code();
    std::string text = R"(This is the first line.
This is the second line.
This is the third line.
)";
    more_code();

Кто-нибудь нашел элегантное решение?

4b9b3361

Ответ 1

Вы можете получить указатель на второй символ - пропустить ведущую новую строку - добавив 1 к const char*, к которому автоматически преобразуется строковый литерал:

    some_code();
    std::string text = 1 + R"(
This is the first line.
This is the second line.
This is the third line.
)";
    more_code();

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

  • удаляет пустую ведущую строку, а
  • смотрит на отступ второй строки и удаляет то же количество отступов из всех дальнейших строк

Это позволяет использовать:

some_code();
std::string text = unindent(R"(
    This is the first line.
    This is the second line.
    This is the third line.
    )");
more_code();

Запись, работающая во время выполнения, относительно проста (см. запустить на ideone.com)...

std::string unindent(const char* p)
{
    std::string result;
    if (p[0] == '\n') ++p;
    const char* p_leading = p;
    while (std::isspace(*p) && *p != '\n')
        ++p;
    size_t leading_len = p - p_leading;
    while (*p)
    {
        result += *p;
        if (*p == '\n')
        {
            ++p;
            for (size_t i = 0; i < leading_len; ++i)
                if (p[i] != p_leading[i])
                    goto dont_skip_leading;
            p += leading_len;
        }
        else
            ++p;
      dont_skip_leading: ;
    }
    return result;
}

... но было бы гораздо лучше выполнить обработку во время компиляции. Я наткнулся на этот пост, в котором упоминается библиотека "constexpr_string" , которая иллюстрирует аналогичную функциональность, но не имела трещина на нем еще....

Ответ 2

Это, вероятно, не то, что вы хотите, но на всякий случай вы должны знать о конкатенации автоматической строки:

    std::string text =
"This is the first line.\n"
"This is the second line.\n"
"This is the third line.\n";

Ответ 3

Я рекомендую ответить @Brian, особенно если вам нужно иметь только несколько строк текста или то, что вы можете обрабатывать с помощью текстового редактора-fu. У меня есть альтернатива, если это не так.

    std::string text =
"\
This is the first line." R"(
This is the second line.
This is the third line.)";

Живой пример

Строковые литералы строки могут конкатенироваться с "нормальными" строковыми литералами, как показано в коде. "\ в начале означает "исключить" символ " из первой строки, поместив его в свою собственную строку.

Тем не менее, если бы я решил, я бы поместил такой лота-текст в отдельный файл и загрузил его во время выполнения. Нет никакого давления на вас, хотя: -).

Кроме того, это один из уродливых кодов, которые я написал в эти дни.

Ответ 4

Самое близкое, что я вижу:

std::string text = ""
R"(This is the first line.
This is the second line.
This is the third line.
)";

Было бы немного лучше, если бы пробел был разрешен в последовательности разделителя. Дайте или возьмите отступы:

std::string text = R"
    (This is the first line.
This is the second line.
This is the third line.
)
    ";

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

Ответ 5

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

/**
 * Strips a multi-line string indentation prefix.
 *
 * Example:
 * \code
 *   string s = R"(|line one
 *                 |line two
 *                 |line three
 *                 |)"_multiline;
 *   std::cout << s;
 * \endcode
 *
 * This prints three lines: @c "line one\nline two\nline three\n"
 *
 * @author Christian Parpart <[email protected]>
 */

inline std::string operator ""_multiline(const char* text, unsigned long size) {
  if (!*text)
    return {};

  enum class State {
    LineData,
    SkipUntilPrefix,
  };

  constexpr char LF = '\n';
  State state = State::LineData;
  std::stringstream sstr;
  char sep = *text++;

  while (*text) {
    switch (state) {
      case State::LineData: {
        if (*text == LF) {
          state = State::SkipUntilPrefix;
          sstr << *text++;
        } else {
          sstr << *text++;
        }
        break;
      }
      case State::SkipUntilPrefix: {
        if (*text == sep) {
          state = State::LineData;
          text++;
        } else {
          text++;
        }
        break;
      }
    }
  }

  return sstr.str();
}

Ответ 6

Принятый ответ выдает предупреждение cppcoreguidelines-pro-bounds-constant-array-index из clang-tidy. См. Pro.bounds: Профиль безопасности Bounds для деталей.

Если у вас нет std::span но вы по крайней мере компилируете с С++ 17, подумайте:

constexpr auto text = std::string_view(R"(
This is the first line.
This is the second line.
This is the third line.
)").substr(1);

Основными преимуществами являются удобочитаемость (IMHO) и то, что вы можете включить это предупреждающее предупреждение в остальной части вашего кода.

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

Ответ 7

Да, это раздражает. Возможно, должны быть необработанные литералы (R"PREFIX(") и многострочные необработанные литералы (M"PREFIX).

Я придумал эту альтернативу, которая почти описывает себя:

#include<iterator> // std::next
...
{
    ...
    ...
    std::string atoms_text = 
std::next/*_line*/(R"XYZ(
  O123        12.4830720891       13.1055820441        9.5288258996
  O123        13.1055820441       13.1055820441        9.5288258996
)XYZ");
    assert( atoms_text[0] != '\n' );
    ...
}

Ограничения:

  1. Если необработанный литерал пуст, он сгенерирует недопустимую строку. Но это должно быть очевидно, чтобы определить.
  2. Если необработанный литерал не начинается с новой строки, вместо него будет использован первый символ.
  3. std::next является constexpr только из С++ 17, затем вы можете использовать 1+(char const*)R"XYZ(" но это не так ясно и может выдавать предупреждение.
constexpr auto atom_text = 1 + (R"XYZ(
  O123        12.4830720891       13.1055820441        9.5288258996
  O123        13.1055820441       13.1055820441        9.5288258996
)XYZ");

Также никаких гарантий;). В конце концов, я не знаю, допустимо ли делать арифметику с указателями на статические данные.