#c #g #c-preprocessor #clang #cl
Вопрос:
Ниже приведен минимальный код для воспроизведения проблемы.
#include <chrono>
#include <functional>
#include <iostream>
using namespace std;
class Test {
public:
Test() {}
void test1() { cout << __func__ << endl; }
void test2() { cout << __func__ << endl; }
void testPrepare() { cout << __func__ << endl; }
private:
};
#define DO_TEST(obj, testName)
{
obj.testPrepare();
std::function<void(void)> test = std::bind(amp;Test::##testName, obj);
/*test();
Some other code which use test()*/
}
int main(int argc, char const *argv[]) {
Test obj;
DO_TEST(obj, test1);
DO_TEST(obj, test2);
/* code */
return 0;
}
Это работает хорошо и делает то, что ожидается с cl.exe , но на g / clang выдает ошибку времени компиляции, как показано ниже:
g d.cpp
d.cpp:19:53: error: pasting "::" and "test1" does not give a valid preprocessing token
19 | std::function<void(void)> test = std::bind(amp;Test::##testName, obj);
| ^~
d.cpp:26:3: note: in expansion of macro ‘DO_TEST’
26 | DO_TEST(obj, test1);
| ^~~~~~~
d.cpp:19:53: error: pasting "::" and "test2" does not give a valid preprocessing token
19 | std::function<void(void)> test = std::bind(amp;Test::##testName, obj);
| ^~
d.cpp:27:3: note: in expansion of macro ‘DO_TEST’
27 | DO_TEST(obj, test2);
| ^~~~~~~
Сведения о компиляторе:
- cl.exe Оптимизирующий компилятор Microsoft (R) C/ C версии 19.29.30133 для x86
- clang версия 10.0.0-4ubuntu1, Цель: x86_64-pc-linux-gnu
- g (Ubuntu 9.3.0-17ubuntu1 ~ 20.04) 9.3.0
Примечание: я знаю, что изменение amp;Test::##testName
на amp;Test:: testName
решит проблему. Но я хочу понять, является ли это ошибкой cl.exe чтобы разрешить приведенный выше код или если g / clang выдает ошибку, это ошибка.
Комментарии:
1. Я думаю, что ошибки верны,
::foo
сами по себе они не являются допустимым токеном, и они должны быть 2 разными токенами.amp;Test::testName
вероятно, это правильно. Не верьте мне на слово, я не уверен на 100%.2. @mediocrevegetable1 итак cl.exe неправильно ли разрешать компиляцию с
amp;Test::##testName
помощью, не так ли?3. ИМО, это неправильно. Опять же, я не уверен на 100%.
4. В дополнение к предложению @mediocrevegetable1: сделайте a
do ... while(0)
вокруг макросов ( пример ), иначе при использовании макросов вы ставите a;
после конца блока}
, что неверно.5. Чтобы включить режим соответствия препроцессору, используйте /Zc:preprocessor . При использовании этой опции компилятор выдает предупреждение.
Ответ №1:
В соответствии со спецификацией C:
каждый экземпляр токена предварительной обработки ## в списке замены удаляется, а предыдущий токен предварительной обработки объединяется со следующим токеном предварительной обработки. … Если результат не является допустимым токеном предварительной обработки, поведение не определено.
Таким образом, использование ##
таким образом, чтобы не создавать ни одного токена, не определено — компилятор может дать диагностику об этом, но не требуется, и вместо этого он может (как расширение) сделать что-то еще.