Можно ли реализовать BOOST_PP_DEFINED?
Можно ли написать функциональный макрос препроцессора C, который возвращает 1
если его аргумент определен, и 0
иначе? Давай называть это BOOST_PP_DEFINED
по аналогии с другими макросами препроцессора boost, которые, как мы можем предположить, также находятся в игре:
#define BOOST_PP_DEFINED(VAR) ???
#define XXX
BOOST_PP_DEFINED(XXX) // expands to 1
#undef XXX
BOOST_PP_DEFINED(XXX) // expands to 0
Я ожидаю использовать результат BOOST_PP_DEFINED
с BOOST_PP_IIF
:
#define MAGIC(ARG) BOOST_PP_IIF(BOOST_PP_DEFINED(ARG), CHOICE1, CHOICE2)
Другими словами, я хочу расширение MAGIC(ARG)
варьироваться в зависимости от того, ARG
определяется или нет в то время, когда MAGIC
расширен:
#define FOO
MAGIC(FOO) // expands to CHOICE1 (or the expansion of CHOICE1)
#undef FOO
MAGIC(FOO) // expands to CHOICE2 (or the expansion of CHOICE2)
Мне также было интересно (и несколько удивительно), что следующее не работает:
#define MAGIC(ARG) BOOST_PP_IIF(defined(arg), CHOICE1, CHOICE2)
Потому что видимо defined
допустимо только в препроцессоре, когда используется как часть #if
выражение.
Я несколько подозреваю, что тот факт, что Boost препроцессор уже не предлагает BOOST_PP_DEFINED
является доказательством его невозможности, но спросить не повредит. Или я упускаю что-то действительно очевидное о том, как этого добиться.
РЕДАКТИРОВАТЬ: Чтобы добавить мотивацию, вот почему я хочу этого. Традиционный способ создания макросов "API" для управления импортом / экспортом заключается в объявлении нового набора макросов для каждой библиотеки, что означает новый заголовок и т. Д. Итак, если у нас есть class Base
в libbase
а также class Derived
в libderived
, тогда у нас есть что-то вроде следующего:
// base_config.hpp
#if LIBBASE_COMPILING
#define LIBBASE_API __declspec(dllexport)
#else
#define LIBBASE_API __declspec(dllimport)
// base.hpp
#include "base_config.hpp"
class LIBBASE_API base {
public:
base();
};
// base.cpp
#include "base.hpp"
base::base() = default;
// derived_config.hpp
#if LIBDERIVED_COMPILING
#define LIBDERIVED_API __declspec(dllexport)
#else
#define LIBDERIVED_API __declspec(dllimport)
// derived.hpp
#include "derived_config.hpp"
#include "base.hpp"
class LIBDERIVED_API derived : public base {
public:
derived();
};
// derived.cpp
#include "derived.hpp"
derived::derived() = default;
Теперь, очевидно, каждый из _config.hpp
заголовок действительно был бы намного более сложным, определяя несколько макросов. Мы могли бы вытащить некоторые общие черты в общий config_support.hpp
файл, но не все. Поэтому, пытаясь упростить этот беспорядок, я подумал, можно ли сделать этот универсальный тип, чтобы можно было использовать один набор макросов, но это будет расширяться по-разному, в зависимости от того, _COMPILING
Макросы были в игре:
// config.hpp
#define EXPORT __declspec(dllexport)
#define IMPORT __declspec(dllimport)
#define API_IMPL2(COND) BOOST_PP_IIF(COND, EXPORT, IMPORT)()
#define API_IMPL(ARG) API_IMPL2(BOOST_PP_DEFINED(ARG))
#define API(LIB) API_IMPL(LIB ## _COMPILING)
// base.hpp
#include "config.hpp"
class API(LIBBASE) base {
public:
base();
};
// base.cpp
#include "base.hpp"
base::base() = default;
// derived.hpp
#include "config.hpp"
#include "base.hpp"
class API(LIBDERIVED) derived : public base {
public:
derived();
};
// derived.cpp
#include "derived.hpp"
derived::derived() = default;
Другими словами, при компиляции base.cpp
, API(LIBBASE)
будет расширяться до __declspec(dllexport)
так как LIBBASE_COMPILING
был определен в командной строке, но при компиляции derived.cpp
API(LIBBASE)
будет расширяться до __declspec(dllimport)
так как LIBBASE_COMPILING
не был определен в командной строке, но API(LIBDERIVED)
теперь расширится до __declspec(dllexport)
поскольку LIBDERIVED_COMPILING
было бы. Но для этого важно, чтобы API
макрос расширяется контекстуально.
3 ответа
Похоже, вы могли бы использовать BOOST_VMD_IS_EMPTY
реализовать требуемое поведение. Этот макрос возвращает 1
если его ввод пуст или 0
если его вход не пустой
Трюк основан на наблюдении, что когда XXX
определяется #define XXX
пустой список параметров передан BOOST_VMD_IS_EMPTY(XXX)
во время расширения.
Пример реализации MAGIC
макрос:
#ifndef BOOST_PP_VARIADICS
#define BOOST_PP_VARIADICS
#endif
#include <boost/vmd/is_empty.hpp>
#include <boost/preprocessor/control/iif.hpp>
#define MAGIC(XXX) BOOST_PP_IIF(BOOST_VMD_IS_EMPTY(XXX), 3, 4)
#define XXX
int x = MAGIC(XXX);
#undef XXX
int p = MAGIC(XXX);
Для Boost 1.62 и VS2015 выход препроцессора будет:
int x = 3;
int p = 4;
Этот подход имеет ряд недостатков, например, он не работает, если XXX
определяется с #define XXX 1
, BOOST_VMD_IS_EMPTY
само по себе имеет ограничения.
РЕДАКТИРОВАТЬ:
Вот реализация необходимого API
макросы на основе BOOST_VMD_IS_EMPTY
:
// config.hpp
#ifndef BOOST_PP_VARIADICS
#define BOOST_PP_VARIADICS
#endif
#include <boost/vmd/is_empty.hpp>
#include <boost/preprocessor/control/iif.hpp>
#define EXPORT __declspec(dllexport)
#define IMPORT __declspec(dllimport)
#define API_IMPL2(COND) BOOST_PP_IIF(COND, EXPORT, IMPORT)
#define API_IMPL(ARG) API_IMPL2(BOOST_VMD_IS_EMPTY(ARG))
#define API(LIB) API_IMPL(LIB ## _COMPILING)
Давайте посмотрим, что препроцессор будет выводить для:
// base.hpp
#include "config.hpp"
class API(LIBBASE) base {
public:
base();
};
когда LIBBASE_COMPILING
определен, выход GCC:
class __attribute__((dllexport)) Base
{
public:
Base();
};
когда LIBBASE_COMPILING
не определено, вывод GCC:
class __attribute__((dllimport)) Base
{
public:
Base();
};
Протестировано с VS2015 и GCC 5.4 (Cygwin)
РЕДАКТИРОВАТЬ 2: Как @acm упоминается, когда параметр определен с -DFOO
это так же, как -DFOO=1
или же #define FOO 1
, В этом случае подход основан на BOOST_VMD_IS_EMPTY
не работает. Чтобы преодолеть это вы можете использовать BOOST_VMD_IS_NUMBER
(Спасибо @jv_ за идею). Реализация:
#ifndef BOOST_PP_VARIADICS
#define BOOST_PP_VARIADICS
#endif
#include <boost/vmd/is_number.hpp>
#include <boost/preprocessor/control/iif.hpp>
#define EXPORT __declspec(dllexport)
#define IMPORT __declspec(dllimport)
#define API_IMPL2(COND) BOOST_PP_IIF(COND, EXPORT, IMPORT)
#define API_IMPL(ARG) API_IMPL2(BOOST_VMD_IS_NUMBER(ARG))
#define API(LIB) API_IMPL(LIB ## _COMPILING)
Это не просто определенная проверка, но мы можем пройти весь путь до проверки конкретного имени токена.
Аннотирование первого принципа решения на основе плаща от Пола Фульца II:
Сначала предоставьте возможность условно выбирать текст на основе расширения макроса до 0 или 1
#define IIF(bit) PRIMITIVE_CAT(IIF_, bit)
#define IIF_0(t, f) f
#define IIF_1(t, f) t
Основная конкатенация
#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__)
#define PRIMITIVE_CAT(a, ...) a##__VA_ARGS__
Логические операторы (комплимент а и)
#define COMPL(b) PRIMITIVE_CAT(COMPL_, b)
#define COMPL_0 1
#define COMPL_1 0
#define BITAND(x) PRIMITIVE_CAT(BITAND_, x)
#define BITAND_0(y) 0
#define BITAND_1(y) y
Метод, чтобы увидеть, является ли токен паренсом "()"
#define CHECK_N(x, n, ...) n
#define CHECK(...) CHECK_N(__VA_ARGS__, 0, )
#define PROBE(x) x, 1,
#define IS_PAREN(x) CHECK(IS_PAREN_PROBE x)
#define IS_PAREN_PROBE(...) PROBE(~)
Примечание IS_PAREN работает, потому что "IS_PAREN_PROBE X" превращается в один аргумент в CHECK(), где "IS_PAREN_PROBE ()" превращается в PROBE(~), который превращается в ~, 1. В этот момент мы можем выбрать 1 из CHECK
Еще одна утилита для съедания некоторых макро аргументов
#define EAT(...)
Здесь мы используем синюю картину (то, что предотвращает наивно-рекурсивные макросы), чтобы проверить, совпадают ли два токена. Если они есть, это рухнет на (). Иначе нет, что мы можем обнаружить через IS_PAREN.
Это зависит от макроса идентификации COMPARE_XXX, существующего для любого данного символа
#define PRIMITIVE_COMPARE(x, y) IS_PAREN(COMPARE_##x(COMPARE_##y)(()))
Мы добавляем черту IS_COMPARABLE для этого помощника
#define IS_COMPARABLE(x) IS_PAREN(CAT(COMPARE_, x)(()))
Мы работаем в обратном направлении к EQUAL, проверяя, сопоставимы ли оба аргумента, а затем преобразуем в primitive_compare, если они есть. Если нет, мы не равны и едим следующие аргументы.
#define NOT_EQUAL(x, y) \
IIF(BITAND(IS_COMPARABLE(x))(IS_COMPARABLE(y))) \
(PRIMITIVE_COMPARE, 1 EAT)(x, y)
EQUAL это комплимент
#define EQUAL(x, y) COMPL(NOT_EQUAL(x, y))
И, наконец, макрос, который мы действительно хотим.
Сначала мы включаем сравнение для "BUILDING_LIB"
#define COMPARE_BUILDING_LIB(x) x
Тогда наш фактический решающий макрос, который является целым числом, если от того, преобразуется ли символ в "BUILDING_LIB"
#define YES_IF_BUILDING_LIB(name) IIF(EQUAL(name, BUILDING_LIB))("yes", "no")
#include <iostream>
#define FOO BUILDING_LIB
int main(int, char**) {
std::cout << YES_IF_BUILDING_LIB(FOO) << "\n";
std::cout << YES_IF_BUILDING_LIB(BAR) << "\n";
}
Какие выводы:
yes
no
Посмотрите его великий пост (который я написал): уловки, советы и идиомы препроцессора
Так как вы собираетесь использовать FOO
в качестве переключателя уровня файла, которым вы управляете, я предлагаю вам использовать более простое решение. Предлагаемое решение легче читать, менее удивительно, не требует грязной магии.
Вместо #define MAGIC(ARG) BOOST_PP_IIF(BOOST_PP_DEFINED(ARG), CHOICE1, CHOICE2)
ты просто -D
с MAGIC=CHOICE1
или же MAGIC=CHOICE2
за файл.
- Вам не нужно делать это для всех файлов. Компилятор скажет вам, когда вы использовали
MAGIC
в файле, но не сделал выбор. - Если
CHOICE1
или жеCHOICE2
является основным значением по умолчанию, которое вы не хотите указывать, вы можете использовать-D
установить значение по умолчанию для всех файлов и-U
+-D
изменить свое решение на файл. - Если
CHOICE1
или жеCHOICE2
долго, вы можете#define CHOICE1_TAG actual_contents
в вашем заголовочном файле, где вы изначально хотели определитьMAGIC
а потом-D
сMAGIC=CHOICE1_TAG
, так какCHOICE1_TAG
будет автоматически расширен вactual_contents
,