Как использовать оператор вставки токена с переменным количеством аргументов? - PullRequest
1 голос
/ 04 апреля 2020

Я думал, что у меня есть c версия #define concatenate(a, b, c) a ## b ## c

Я попробовал это так:

#include <stdio.h>

#define concatenate(arg1, ...) arg1 ## __VA_ARGS__

int main()
{
    int dob = 121201;
    printf("%d", concatenate(d, o, b));

    return 0;
}

Я также пробовал много других способов:

#define concatenate(arg1, ...) arg1 ## ##__VA_ARGS__

#define concatenate(...) ## ##__VA_ARGS__

#define concatenate(...) ##__VA_ARGS__

#define concatenate(arg1, ...) arg1 ## ...

#define concatenate(arg1, ...) arg1 ## concatenate(##__VA_ARGS__)

Увы, все мои попытки провалились. Мне было интересно, возможно ли вообще сделать это каким-либо образом?

1 Ответ

1 голос
/ 05 апреля 2020

Это возможно. Jens Gustedt интересная библиотека макросов P99 включает в себя макрос P99_PASTE, который имеет именно вашу подпись concatenate, а также семантику той же .

Механика, которую P99 использует для реализации этой функции, по меньшей мере, сложна. В частности, они полагаются на несколько сотен пронумерованных макросов, которые компенсируют тот факт, что препроцессор C не допускает рекурсивное расширение макросов.

Еще одно полезное объяснение того, как выполнять итерацию в препроцессоре C, можно найти в документации для библиотеки Boost Preprocessor , в частности, в topi c on reentrancy .

Документация Дженса для P99_PASTE подчеркивает тот факт, что макрос вставляется слева направо, чтобы избежать неоднозначности ##. Это может нуждаться в небольшом объяснении.

Оператор Token-Paste (##) является двоичным оператором; если вы хотите вставить более двух сегментов в один токен, вам нужно делать это по паре за раз, что означает, что все промежуточные результаты должны быть действительными токенами. Это может потребовать определенной осторожности. Рассмотрим, например, этот макрос, который пытается добавить экспоненту в конец целого числа:

#define EXPONENT(INT, EXP) INT ## E ## EXP

(Это будет работать только в том случае, если оба аргумента макроса являются литеральными целыми числами. Чтобы разрешить аргументам макроса чтобы быть макросом, нам нужно было бы ввести другой уровень косвенности в расширении макроса. Но здесь дело не в этом.)

Мы почти сразу обнаружим, что EXPONENT(42,-3) не работает, потому что -3 это не один токен. Это два токена, - и 3, и оператор вставки вставит только -. Это приведет к последовательности из двух токенов 42E- 3, что в конечном итоге приведет к ошибке компилятора. Кстати,

42E и 42E- являются действительными токенами. Это ppnumbers , числа предварительной обработки, представляющие собой любую комбинацию точек, цифр, букв и показателей степени, при условии, что токен начинается с ди git или точки, за которой следует ди git. (Экспоненты - это одна из букв E или P, возможно, строчных и, возможно, сопровождаемых знаком. В противном случае знаки знака не могут появляться в ppnumber .)

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

#define EXPONENT(INT, SIGN, EXP) INT ## E ## SIGN ## EXP

EXPONENT(42,-,3)

Это сработает, если операторы ## вычисляются слева направо. Но стандарт C не предусматривает какого-либо конкретного порядка оценки нескольких операторов ##. Если мы используем препроцессор, который работает справа налево, то первое, что он попытается сделать, это вставить - и 3, что не сработает, потому что -3 это не один токен, просто как с более простым определением.

Теперь я не могу предложить пример компилятора, который потерпит неудачу в этом макросе, так как у меня нет удобного препроцессора справа налево. И g cc, и clang оценивают ## слева направо, и я думаю, что это самый распространенный порядок оценки. Но вы не можете полагаться на это; для написания переносимого кода необходимо убедиться, что операторы вставки вычисляются в ожидаемом порядке. И это гарантия, предоставляемая P99_PASTE.

Примечание : возможно, существует приложение, в котором требуется вставка справа налево, но после обдумывания этого в течение некоторого времени Единственный пример, который я мог бы придумать, чтобы вставить токен, который работал бы справа налево, но не слева направо, это следующий довольно неясный угловой случай:

#define DOUBLE_HASH %: ## % ## :

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...