Можно ли реализовать 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.cppAPI(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,
Другие вопросы по тегам