Что должно произойти в C ++ 11 в первую очередь: расширение исходной строки или макросы?

Этот код работает в Visual C ++ 2013, но не в gcc / clang:

#if 0
R"foo(
#else
int dostuff () { return 23; }
// )foo";
#endif
dostuff();

Visual C ++ сначала удаляет if 0. Clang сначала расширяет необработанную строку R (и никогда не определяет dostuff). Кто прав и почему?


person starmole    schedule 23.06.2015    source источник
comment
Эта ссылка на этап перевода расскажет вам об этом.   -  person Some programmer dude    schedule 23.06.2015
comment
Согласно ссылке Иоахима, фаза 3 (токенизация) происходит до фазы 4 (препроцессор). IOW, код недействителен.   -  person Dummy00001    schedule 23.06.2015
comment
Я не хочу сейчас добавлять еще один вопрос, но есть идеи, как заставить clang вести себя как vcc? Для меня предварительная процедура была бы более полезной.   -  person starmole    schedule 23.06.2015
comment
@starmole: чем это полезно? Почему бы просто не #if ‹whatever› ... #else ... #endif без строкового литерала?   -  person Tony Delroy    schedule 23.06.2015
comment
@TonyD: Идея состоит в том, чтобы включить код как в виде кода, так и в виде строки. Одним из забавных вариантов использования может быть учебное пособие, которое может одновременно запускать и отображать исходный код. В моем случае это были шейдеры GLSL, которые должны быть строковыми в режиме графического процессора для отправки графическому драйверу, но должны компилироваться как cpp при программной эмуляции.   -  person starmole    schedule 23.06.2015
comment
@starmole: вы знаете об операторе # для макросов? Вы можете преобразовать аргументы макроса в строку и заставить их создавать строковые литералы и / или код. По общему признанию, это может быть сложно, когда есть значения, разделенные запятыми, но иногда помогает __VA_ARGS__.   -  person Tony Delroy    schedule 23.06.2015
comment
@TonyD: Или вы можете поместить избыточный () вокруг единственного аргумента, затем преобразовать его в строку и удалить первый и последний символы из результата в виде строки.   -  person rici    schedule 23.06.2015


Ответы (2)


[Обновление: ниже в комментариях Адриана Маккарти говорится, что это исправлено в MSVC ++ 2017]

GCC и clang правы, VC ++ ошибочен.

2.2 Этапы перевода [lex.phases]:

[...]

  1. Исходный файл разбивается на токены предварительной обработки (2.5) и последовательности символов пробела (включая комментарии).

  2. Директивы предварительной обработки выполняются, [...]

И 2.5 Предварительная обработка токенов [lex.pptoken] перечисляет string-literals среди токенов.

Следовательно, требуется синтаксический анализ, чтобы сначала разметить строковый литерал, используя определение функций #else и dostuff.

person Tony Delroy    schedule 23.06.2015
comment
Понятно! Спасибо Иоахиму Пилеборгу выше за ссылку. Так что визуальный C неверен! - person starmole; 23.06.2015
comment
@starmole: многие старые компиляторы раньше делали много подобных вещей неправильно, но MS VC ++, в частности, выделяется тем, что редко исправляет подобные вещи, поскольку они не хотят рисковать, нарушая массу кода, написанного для их компиляторов, который может как-то зависеть от существующего поведения. - person Tony Delroy; 23.06.2015
comment
Чтобы добавить к @TonyD: особенно в странных крайних случаях, с которыми люди вряд ли когда-либо столкнутся. - person user253751; 23.06.2015
comment
MSVC ++ 2017, кажется, поступает правильно. Необработанные строковые литералы были новинкой в ​​MSVC ++ 2013. Препроцессор MSVC долгое время имел нестандартное поведение, которое сохранялось как есть для обратной совместимости. Но в последнее время даже препроцессор получает лечение соответствия стандартам. blogs.msdn.microsoft.com / vcblog / 2018/07/06 / - person Adrian McCarthy; 28.01.2019
comment
@AdrianMcCarthy: о, круто, я соответствующим образом отредактировал свой ответ. Ваше здоровье - person Tony Delroy; 29.01.2019

Я подумал, что стоит повторить интересную "причуду" фазы лексирования. Содержимое внутри #if 0 ... #else не игнорируется, как вы наивно представляете (я был наивен, пока не проверил это). Вот два примера, разница просто в дополнительном пробеле между R и " в объявлении необработанной строки, которое находится внутри блока #if 0.

#include <iostream>
using namespace std;

#if 0 
const char* s = R"(
#else
int foo() { return 3; }
// )";
#endif

int main() {
    std::cout << foo() << std::endl;
    return 0;
}

Результаты в (gcc 6.3, C ++ 14)

prog.cpp: In function ‘int main()’:
prog.cpp:12:19: error: ‘foo’ was not declared in this scope
  std::cout << foo() << std::endl;

Добавление символа пробела (в коде, который якобы игнорируется компилятором!) Позволяет ему скомпилировать:

#include <iostream>
using namespace std;

#if 0 
const char* s = R "(
#else
int foo() { return 3; }
// )";
#endif

int main() {
    std::cout << foo() << std::endl;
    return 0;
}

Компилируется и запускается с

3

Обратите внимание, что использование традиционного строкового литерала, не являющегося исходным, не вызывает этой проблемы. Вам не разрешено разделять необработанную строку на новую строку, поэтому в этом случае необработанная строка игнорируется и не токенизируется. Итак, если вы избавились от R, он компилируется просто в файл.

Очевидно, что безопаснее всего не позволять вашей необработанной строке пересекать границу препроцессора.

person Mark Lakata    schedule 28.01.2019