不知道宏数量的情况下打印宏值

31

我有一些代码,其中包括一个已生成的文件(我事先不知道它的内容),只有我和我的用户约定了如何创建这个文件,以便我可以使用它。这个文件看起来像

#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
我的问题是如何在生成的文件中迭代宏,以便我不需要重复那么多代码。

使用您选择的编程语言编写一个简短的预处理脚本来生成您的代码块。在编译之前运行它。如果您被您和用户使用的文件逼入了墙角,这是唯一明智的做法,在我看来。 - StoryTeller - Unslander Monica
1
如果“真正的代码”更加复杂,谁能说这里提出的任何建议都适用呢? - StoryTeller - Unslander Monica
3
如果生成包含MACROn的文件在你的控制之下,为什么不进一步增强它来解决这个问题呢? - GManNickG
如果您无法使用文件格式,为什么要同意使用该文件格式? - user743382
历史上,当我开始开发系统时,我愿意接受代码重复,以使系统按预期工作。随着时间的推移,代码变得越来越复杂,我希望减少代码重复,使其更易于维护。我不想改变文件格式,因为我有义务向用户保证不会干扰他们,而是要学习我们达成的约定。 - e271p314
显示剩余2条评论
3个回答

55

首先,我们知道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)

... voilà!

在Coliru上实时查看

注意:该Coliru代码片段包括GCC和Clang的警告禁用器,这些警告针对我们可怜的最恼人的解析 :(


21
你利用了最令人烦恼的语法分析...我感觉应该鞠躬并诵念自己的不配之词。 - StoryTeller - Unslander Monica
15
这是传奇。 - M.M
2
我们在这里充分利用了我们的老朋友最头疼的解析。这真是纯粹的天才! - YSC
1
可以避免烦恼的解析的类似方法是使用字符串拼接来生成缺失宏的空字符串,这些字符串可以很容易地进行测试,假设您定义的宏中没有空值:MACRO1 "" ==> "A" MACRO401 "" ==> "" - Gem Taylor
1
@Quentin - 我原本评论时并不是在开玩笑 :) 这太酷了! - StoryTeller - Unslander Monica
显示剩余5条评论

3

很久以前我遇到了同样的需求。

我的解决方案是使用预处理器,但不是在代码中得出答案。

例如,clang++ -dM -E test.cpp 将输出所有宏。(当时我使用的是 gcc,但同样的技术适用于 GCC、CLang 和 Visual Studio 的 CL.EXE...编译器开关可能有所不同。)

啊,该死的,这也包括了所有预定义的宏。

因此,我会创建一个“黑名单”文件,其中包含我不关心的预定义宏,然后使用 grep -v 来过滤掉那些结果。

我遇到的另一个问题是,有时候有人会使用 #undef IMPORTANT_MACRO,这样就会错过导出的信息。对于那些不经常出现的情况...然后谋杀就开始了


0

这个答案考虑了后续问题

C++支持通用编程,通常可以消除预处理器的需求。在这种情况下,最好建立一组类型特征,声明需要处理的参数属性,以减少预处理器的作用,将其降为条件编译(如果此代码应该每次都生成,则完全消除它):

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;
}

在线编译器


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接