VMD имеет ряд специфических макросов для анализа типов данных. Каждый из этих макросов спрашивает, является ли некоторый ввод определенным типом данных VMD.
Можно передать пустой аргумент макро. Официальная терминология для этого в стандарте C++ является аргументом, «состоящим из отсутствия токенов предварительной обработки».
Давайте рассмотрим ряд случаев, не слишком беспокоясь о том, что представляет собой макро-выход.
Рассмотрим эти два функциональных макроса:
#define SMACRO() someoutput
#define EMACRO(x) otheroutput x
Первый макрос не принимает никаких параметров, поэтому его всегда нужно использовать.
SMACRO()
И любые доводы в пользу этого будут недействительными.
Второй макрос принимает один параметр. Его можно вызвать как
EMACRO(somedata)
Он также может быть использован как
EMACRO()
Во втором обращении к EMACRO мы передаем макросу пустой аргумент. Аналогично для любого макроса, имеющего 1 или более параметров, пустой аргумент может быть достоверно передан для любого из параметров.
#define MMACRO(x,y,z) x y z
MMACRO(1,,2)
Пустой аргумент — это аргумент, даже если мы ничего не передаем.
Поскольку для данного параметра макроса может быть принят пустой аргумент, это не означает, что нужно это делать. Любой данный макрос определит, что должен представлять каждый аргумент для макроса, и обычно очень редко встречается макрос, который указывает, что пустой аргумент может логически быть передан для данного аргумента. Но с точки зрения стандартного C++ вполне допустимо передать пустой аргумент для макропараметра.
Понятие передачи пустых аргументов может быть распространено на передачу пустых данных, которые «не состоят из токенов предварительной обработки» в несколько более сложных ситуациях. Возможна передача пустых данных в качестве аргумента вариадному макросу в виде вариадных макроданных.
#define VMACRO(x,...) x __VA_ARGS__
Ссылаясь как
VMACRO(somedata,)
Здесь вы передаете пустые данные в виде вариадных макроданных, и это совершенно справедливо для C++. Обратите внимание, что это отличается от
VMACRO(somedata)
С++ недействителен, так как что-то должно быть передано для вариадного аргумента. Похожие можно было бы назвать макро как
VMACRO(somedata,vdata1,,vdata3)
где вы передаете вариадные макроданные, но элемент в вариадных макроданных пуст.
Кроме того, если мы ссылаемся на макрос, который ожидает тип данных Boost PP, такой как кортеж, мы также можем достоверно передавать пустые данные для всех или части данных в кортеже, как в случае с фиксированными данными.
#define TMACRO(x,atuple) x atuple
TMACRO(somedata,())
В этом случае мы проходим 1 элементный кортеж, где сам элемент пуст.
или
TMACRO(somedata,(telem1,,telem2,teleem3))
В этом случае мы проходим 4-элементный кортеж, где второй элемент пуст.
Опять же, любой вызов действителен для C++, но это не обязательно то, что требуется для макроса, даже если в обоих случаях макродизайнер указал, что второй параметр должен быть набором для правильной работы макроса.
Подобно передаче пустых аргументов различными способами на макрос, данные, которые макрос возвращает (или «генерирует» может быть лучшим термином), могут быть пустыми. Снова Я не обязательно пропагандирую эту идею как обычное явление макродизайна, а просто укажу на нее как на действительную предварительную обработку C++.
#define RMACRO(x,y,z)
RMACRO(data1,data2,data3)
Вполне допустимо, что C++ возвращает «ничто» из макроссылки. Фактически, ряд макросов в Boost PP делают это на основе логики метапрограммирования препроцессора макроса и документируются как таковые.
Точно так же нельзя вернуть ничего как часть или весь тип данных Boost PP или даже как часть вариадных макроданных.
#define TRETMACRO(x,y,z) ()
#define TRETMACRO1(x,y,z) (x,,y,,z)
#define VRETMACRO(x,y,z) x,,y,,z
Здесь мы снова возвращаем что-то, но с точки зрения набора Boost PP или с точки зрения вариадных данных у нас есть элементы, которые пусты.
В приведенных выше примерах, где «пустота» в той или иной форме передается в качестве аргументов макросу или возвращается из макроса, приведенные мною примеры были созданы настолько упрощенными, насколько это возможно, чтобы проиллюстрировать мои точки зрения. В реальном препроцессорном метапрограммировании с использованием Boost PP, где сложная логика используется для генерации макровых выводов на основе аргументов макроса, было бы полезно разрешить и работать с пустыми данными, если бы можно было проверить тот факт, что данные действительно пусты.
В настоящее время Boost PP имеет недокументированный макрос для проверки того, пуст ли параметр, написанный без использования вариадных макросов. Макро называется BOOST_PP_IS_EMPTY. Макрос по своей природе несовершенен, поскольку нет обобщенного способа определения того, является ли параметр пустым с помощью препроцессора C++. Но макрос будет работать, если ввод ограничен различными способами или если ввод фактически пуст.
Пол Менсонидес, разработчик Boost PP и макроса BOOST_PP_IS_EMPTY в этой библиотеке, также написал лучший макрос, используя вариадные макросы, для определения того, является ли параметр пустым или нет, который он опубликовал в Интернете в ответ на дискуссию о пустоте. Этот макрос также не идеален, так как нет идеального решения, но будет работать правильно практически со всеми входными данными. Я адаптировал его код для VMD и разработал свой собственный немного другой код.
Макро называетсяBOOST_VMD_IS_EMPTY
и вернет 1, если вход пуст или 0, если вход не пуст. Макро — это вариадный макрос, который принимает любой вход.
Одна ситуация, когда макрос всегда не работает должным образом, заключается в том, что его ввод решается на функциональное макро-имя или последовательность препроцессорных токенов, заканчивающихся функциональным макро-имя, и функциональный макрос принимает два или более параметров.
Вот простой пример:
#include <boost/vmd/is_empty.hpp>
#define FMACRO(x,y) any_output
BOOST_VMD_IS_EMPTY(FMACRO)
BOOST_VMD_IS_EMPTY(some_input FMACRO)
В первом случае имя функционального макроса передается BOOST_VMD_IS_EMPTY, а во втором случае последовательность препроцессинговых токенов передается BOOST_VMD_IS_EMPTY, заканчиваясь именем функционального макроса. Функциональный макрос также имеет два (или более) параметра. В обоих случаях вышеупомянутая ошибка компилятора является результатом использования BOOST_VMD_IS_EMPTY.
Обратите внимание, что эти два проблемных случая не совпадают с вызовом функционального макро-имя на BOOST_VMD_IS_EMPTY.
#include <boost/vmd/is_empty.hpp>
BOOST_VMD_IS_EMPTY(FMACRO(arg1,arg2))
BOOST_VMD_IS_EMPTY(someinput FMACRO(arg1,arg2))
Это всегда работает правильно, если, конечно, конкретная функция, подобная макро-призыву, не решает ни одну из наших двух предыдущих ситуаций.
Другая ситуация, когда макрос может не работать должным образом, заключается в том, что ранее упомянутый функциональный макрос принимает один параметр, но создает ошибку, когда принятый аргумент пуст. Примером этого может служить:
#define FMACRO(x) BOOST_PP_CAT(+,x C);
Когда FMACRO ничего не передает в неопределенное поведение, происходит попытка соотнести «+» с «С» в терминах препроцессора C++.
Таким образом, для стандартного компилятора мы имеем, по существу, единый угловой случай, когда BOOST_VMD_IS_EMPTY не работает и, когда он не работает, производит ошибку компилятора, а не неправильный результат. По сути, для максимальной безопасности желательно, чтобы при тестировании на пустоту мы никогда не пропускали вход, заканчивающийся названием функционально-подобного макро-имя.
Препроцессор VC++ не является стандартным препроцессором, соответствующим C++, по крайней мере, в двух соответствующих ситуациях. Эти ситуации объединяются, чтобы создать единый угловой случай, который заставляет макрос BOOST_VMD_IS_EMPTY не работать должным образом с использованием VC++, когда вход решается на функциональное макро-имя.
Первая ситуация, связанная с нашим обсуждением пустоты, когда препроцессор VC++ не является стандартным препроцессором, соответствующим C++, заключается в том, что если макрос, принимающий число параметров n, вызывается с параметрами от 0 до n-1, компилятор не дает ошибки, а только предупреждение.
#define FMACRO(x,y) x + y
FMACRO(1)
должен давать ошибку компилятора, как это происходит при использовании компилятора, отвечающего стандарту C++, но при вызове с использованием VC++; Предупреждение и VC++ Продолжается макрозамещение с «y» в качестве маркера предварительной обработки токена. Это нестандартное действие фактически устраняет случай, когда BOOST_VMD_IS_EMPTY не работает должным образом со стандартным компилятором, соответствующим C++. Но, конечно, он имеет потенциал получения неправильного вывода в других ситуациях макрообработки, не связанных с вызовом BOOST_VMD_IS_EMPTY, где должна произойти ошибка компилятора.
Вторая общая ситуация, связанная с нашей дискуссией о пустоте, где препроцессор VC++ не является стандартным препроцессором, соответствующим C++, заключается в том, что расширение макроса работает неправильно, когда расширенный макрос является функционально-подобным макро-имя, за которым следует функционально-подобное макро-призыв, и в этом случае макроповторное расширение ошибочно выполняется более одного раза. Последний случай можно увидеть на этом примере:
#define FMACRO1(parameter) FMACRO3 parameter()
#define FMACRO2() ()
#define FMACRO3() 1
FMACRO1(FMACRO2)
should expand to:
FMACRO3()
but in VC++ it expands to:
1
где после первоначального расширения макроса:
FMACRO3 FMACRO2()
VC++ ошибочно сканирует последовательность токенов предварительной обработки более одного раза, а не сканирует еще один раз для большего количества макро-имен.
Эти два конкретных недостатка препроцессора в компиляторе VC++ означают, что, хотя BOOST_VMD_IS_EMPTY не терпит неудачу с ошибкой компилятора в том же случае, что и со стандартным компилятором, соответствующим C++, приведенным ранее, он терпит неудачу, давая неправильный результат в другой ситуации.
Неудачная ситуация такова:
Когда ввод в BOOST_VMD_IS_EMPTY разрешает только функциональное макро-имя, а функциональный макрос при прохождении одного пустого аргумента расширяется до Boost PP tuple, BOOST_VMD_IS_EMPTY будет ошибочно возвращать 1 при использовании компилятора Visual C++, а не давать ошибку предварительной обработки или возвращать 0.
Вот пример неудачи:
#include <boost/vmd/is_empty.hpp>
#define FMACRO4() ( any_number_of_tuple_elements )
#define FMACRO5(param) ( any_number_of_tuple_elements )
#define FMACRO6(param1,param2) ( any_number_of_tuple_elements )
BOOST_VMD_IS_EMPTY(FMACRO4)
BOOST_VMD_IS_EMPTY(FMACRO5)
BOOST_VMD_IS_EMPTY(FMACRO6)
Как и в случае со стандартным компилятором, соответствующим C++, у нас есть редкий угловой случай, когда BOOST_VMD_IS_EMPTY не будет работать должным образом, но, к сожалению, в этом очень похожем, но еще более редком угловом случае с VC++ мы получим неправильный результат, а не ошибку компилятора.
Я хочу повторить, что в C++ нет идеального решения для обнаружения пустоты даже для компилятора C++, чей препроцессор полностью совместим, чего, очевидно, нет в VC++.
Со всеми вышеперечисленными случаями, когда BOOST_VMD_IS_EMPTY будет работать неправильно, очень мало, даже с ошибочным препроцессором VC++, и я считаю макрос стоит использовать, поскольку он работает правильно с подавляющим большинством возможных входов препроцессора.
Случай, когда он не будет работать, как со стандартом C++, соответствующим препроцессору, так и со стандартом Visual C++, возникает, когда имя функционального макроса является частью ввода в BOOST_VMD_IS_EMPTY. Очевидно, что макрос должен использоваться препроцессорным метапрограммером, когда возможный вход в него ограничен для устранения ошибочного случая.
Кроме того, поскольку пустота может быть правильно протестирована практически в каждой ситуации, макрос BOOST_VMD_IS_EMPTY может использоваться внутри, когда метапрограммист препроцессора хочет вернуть данные из макроса, и все или часть этих данных могут быть пустыми.
Поэтому я считаю макрос BOOST_VMD_IS_EMPTY весьма полезным, несмотря на угловой недостаток, который делает его несовершенным. Следовательно, я считаю, что препроцессорный метапрограммист может использовать концепцию пустых препроцессорных данных при разработке своих собственных макросов.
Макро BOOST_VMD_IS_EMPTY используется внутри VMD, и макропрограммисты могут найти этот макрос полезным в своих собственных усилиях по программированию, несмотря на небольшой недостаток в том, как он работает.
Вы можете использовать общий файл заголовка:
#include <boost/vmd/vmd.hpp>
Вы можете использовать индивидуальный файл заголовка:
#include <boost/vmd/is_empty.hpp>
Для макроса BOOST_VMD_IS_EMPTY