Подтвердить что ты не робот

Печать значений макроса без знания количества макросов

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

#define MACRO0 "A"
#define MACRO1 "B"
#define MACRO2 "C"
...

Я хочу напечатать все значения макросов. Мой текущий код выглядит как

#ifdef MACRO0
std::cout << "MACRO0 " << MACRO0 << std::endl;
#endif
#ifdef MACRO1
std::cout << "MACRO1 " << MACRO1 << std::endl;
#endif
#ifdef MACRO2
std::cout << "MACRO2 " << MACRO2 << std::endl;
#endif

Мой вопрос в том, как перебирать макросы в сгенерированном файле, поэтому мне не нужно дублировать мой код так много

4b9b3361

Ответ 1

Прежде всего, мы знаем, что мы можем рассчитывать на Boost.Preprocessor для наших потребностей цикла. Однако сгенерированный код должен работать сам по себе. К сожалению, #ifdef не может работать в результате расширения макросов, поэтому нет способа генерировать код в вашем вопросе. Мы поджариваем?

Пока нет! Мы можем воспользоваться тем фактом, что ваши макросы все либо несуществующие, либо строковые литералы. Рассмотрим следующее:

using StrPtr = char const *;
StrPtr probe(StrPtr(MACRO1));

Мы используем наш старый друг самым неприятным анализом. Вторая строка может быть интерпретирована двумя способами в зависимости от того, определена ли MACRO1. Без него это эквивалентно:

char const *probe(char const *MACRO1);

... который является объявлением функции, где MACRO1 - это имя параметра. Но, когда MACRO1 определяется как "B", он становится эквивалентным:

char const *probe = (char const *) "B";

... который является переменной, инициализированной точкой в ​​ "B". Затем мы можем включить тип того, что мы только что создали, чтобы увидеть, произошла ли замена:

if(!std::is_function<decltype(probe)>::value)
    std::cout << "MACRO1 " << probe << '\n';

Здесь мы могли бы использовать if constexpr, но std::cout может выводить указатель на функцию (он преобразует его в bool), поэтому мертвая ветка действительна, а компилятор достаточно умен, чтобы полностью оптимизировать его.

Наконец, мы возвращаемся к Boost.Preprocessor для создания всего этого для нас:

#define PRINT_IF_DEFINED(z, n, data) \
    { \
        StrPtr probe(StrPtr(BOOST_PP_CAT(MACRO, n))); \
        if(!std::is_function<decltype(probe)>::value) \
            std::cout << "MACRO" BOOST_PP_STRINGIZE(n) " " << probe << '\n'; \
    }

#define PRINT_MACROS(num) \
    do { \
    using StrPtr = char const *; \
    BOOST_PP_REPEAT(num, PRINT_IF_DEFINED, ~) \
    } while(false)

... вуаля!

Посмотрите вживую на Coliru

Примечание: фрагмент Coliru включает предупреждающие разделители для GCC и Clang, которые предупреждают нашего бедного приятеля о наиболее неприятном анализе: (

Ответ 2

Я столкнулся с такой же необходимостью давно.

Мое решение состояло в том, чтобы использовать препроцессор, но не для того, чтобы получить ответ "внутри кода".

Например, clang++ -dM -E test.cpp выведет все макросы. (В то время я использовал gcc, но тот же метод работает для GCC, CLang и Visual Studio CL.EXE... ключи компилятора могут отличаться.)

Ahh, drat, который также включает все предопределенные макросы.

Итак, я бы создал файл "черного списка" из предопределенных макросов, который мне не нравился, а затем использовал его для фильтрации этих результатов (используя grep -v).

Другая проблема, с которой я столкнулся, заключалась в том, что иногда кто-то мог бы #undef IMPORTANT_MACRO, который затем пропустил бы в дампе. Для тех редких ситуаций... и затем начались убийства.

Ответ 3

Этот ответ написан с учетом последующего вопроса.

С++ поддерживает универсальное программирование, которое часто устраняет необходимость в препроцессоре. В этом случае было бы лучше создать набор признаков типа, объявляющих свойства параметров, которые необходимо обработать, уменьшая роль препроцессора до условной компиляции (или полностью исключая его, если этот код должен генерироваться каждый раз):

enum class
t_Param
{
    begin, a = begin, b, c, d, e, z, end
};

template<t_Param param, typename TEnabled = void> class
t_ParamIsEnabled final: public ::std::true_type
{};

template<t_Param param> class
t_ParamIsEnabled
<
    param
,   typename ::std::enable_if
    <
        (t_Param::end == param)
        #ifndef A1
        || (t_Param::a == param)
        #endif
        #ifndef B2
        || (t_Param::b == param)
        #endif
        #ifndef C3
        || (t_Param::c == param)
        #endif
        #ifndef D4
        || (t_Param::d == param)
        #endif
        #ifndef E5
        || (t_Param::e == param)
        #endif
    >::type
> final: public ::std::false_type
{};

template<t_Param param> class
t_ParamTrait;

template<> class
t_ParamTrait<t_Param::a> final
{
    public: static constexpr auto const & num{"1"};
    public: static constexpr auto const & val{"A"};
};

template<> class
t_ParamTrait<t_Param::b> final
{
    public: static constexpr auto const & num{"2"};
    public: static constexpr auto const & val{"B"};
};

template<> class
t_ParamTrait<t_Param::c> final
{
    public: static constexpr auto const & num{"3"};
    public: static constexpr auto const & val{"C"};
};

template<> class
t_ParamTrait<t_Param::d> final
{
    public: static constexpr auto const & num{"4"};
    public: static constexpr auto const & val{"D"};
};

template<> class
t_ParamTrait<t_Param::e> final
{
    public: static constexpr auto const & num{"5"};
    public: static constexpr auto const & val{"E"};
};

template<> class
t_ParamTrait<t_Param::z> final
{
    public: static constexpr auto const & num{"26"};
    public: static constexpr auto const & val{"ZZ"};
};

Это позволит вам перебирать параметры и запрашивать их свойства с помощью общего кода:

template<t_Param param> typename ::std::enable_if<t_ParamIsEnabled<param>::value>::type
Echo(void)
{
    ::std::cout << t_ParamTrait<param>::val << ":" << t_ParamTrait<param>::num << ::std::endl;
}

template<t_Param param> typename ::std::enable_if<!t_ParamIsEnabled<param>::value>::type
Echo(void)
{
    //  Do nothing
}

template<int param_index = 0> void
Echo_All(void)
{
    Echo<static_cast<t_Param>(param_index)>();
    Echo_All<param_index + 1>();
}

template<> void
Echo_All<static_cast<int>(t_Param::end)>(void)
{
    //  Do nothing.
}

int main()
{
    Echo_All();
    return 0;
}

онлайн-компилятор