【问题标题】:Variadic macro expanding可变参数宏扩展
【发布时间】:2013-11-18 03:26:22
【问题描述】:

我想知道有什么方法可以选择性地调用 C VARIADIC 宏。

首先,让我展示一些我想要实现的代码:

#include <stdio.h>

#define _VA_NARGS_IMPL(_1,_2,_3,_4,_5,_6,_7,_8,N,...) N
#define _VA_NARGS(...) _VA_NARGS_IMPL(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1) 
#define binder(count, ...) arg##count(__VA_ARGS__)
#define foo(...) binder(_VA_NARGS(__VA_ARGS__), __VA_ARGS__)
#define arg1(_1) _1
#define arg2(_1, _2) _1, _2
#define arg3(_1, _2, _3) _1, _2, _3

int main()
{
    printf("%d %d %d", foo(11,22,33));
    return 0;
}

我在 VC11、GCC4.8 和 Clang 3.4 中对其进行了测试,但没有一个可以按照我的意愿编译它。

是的,我想通过参数计数来调用宏,但是宏被扩展为:

foo(...)
binder(count, ...)
arg_VA_NAGS(...)

没有什么诀窍吗?


编辑:

我更详细地写了我真正想要的东西。

我从答案中找到了一些线索并编辑了我的代码。

typedef unsigned short ListHeader;

template<typename T>
inline const size_t GetSize(const T& _obj) {return sizeof(T);}

inline const size_t GetSize(const std::string& _str) {return sizeof(ListHeader) + _str.size() + 1;}

inline const size_t GetSize(const std::vector<std::string>& _vec)
{
    size_t total = 0;

    for (auto item : _vec)
    {
        total += GetSize(item);
    }

    return sizeof(ListHeader) + total;
}

template<typename T>
inline const size_t GetSize(const std::vector<T>& _vec)
{
    size_t total = 0;

    for (auto item : _vec)
    {
        total += GetSize<decltype(item)>(item);
    }

    return sizeof(ListHeader) + total;
}

#define VA_NARGS_IMPL(_1,_2,_3,_4,_5,_6,_7,_8,N,...) N
#define VA_NARGS(...) VA_NARGS_IMPL(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1)


#define VARARG_IMPL2(base, count, ...) base##count(__VA_ARGS__)
#define VARARG_IMPL(base, count, ...) VARARG_IMPL2(base, count, __VA_ARGS__) 
#define VARARG(base, ...) VARARG_IMPL(base, VA_NARGS(__VA_ARGS__), __VA_ARGS__)



#define SerialSize(...) VARARG(SerialSize, __VA_ARGS__)



#define SerialSize1(_1) \
    const size_t size() {return GetSize(_1);}
#define SerialSize2(_1,_2) \
    const size_t size() {return GetSize(_1) + GetSize(_2);}
#define SerialSize3(_1,_2,_3) \
    const size_t size() {return GetSize(_1) + GetSize(_2) +  GetSize(_3);}
#define SerialSize4(_1,_2,_3,_4) // same implementation except count of arguments: 1..4
#define SerialSize5(_1,_2,_3,_4,_5) // 1...5
#define SerialSize6(_1,_2,_3,_4,_5,_6) //1...6
#define SerialSize7(_1,_2,_3,_4,_5,_6,_7) //1...7
#define SerialSize8(_1,_2,_3,_4,_5,_6,_7,_8) //1..8


// Please don't care about detailed implementation of my Archive class.
// It's not important now I guess..
class Archive
{
public:
    template<typename T>
    Archive& operator, (T& _val) //comma operator for Variadic macro
    {
        if (reading)
            read(&_val);
        else
            write(&_val);

        return *this;
    }

    Archive& out();
    Archive& in();

private:

    template<typename T>
    Archive& read(T&);
    template<typename T>
    Archive& write(T&);
};



class Serializable
{
public:
    Serializable(void) {}
    virtual ~Serializable(void) {}

    virtual const size_t size() = 0;
    virtual void serialize(Archive&) = 0;
    virtual void deserialize(Archive&) = 0;
};


#define SerialFormat(...) \
    SerialSize(__VA_ARGS__) \
    void serialize(Archive& ar)\
    {\
        ar.out() , ##__VA_ARGS__ ;\
    }\
    void deserialize(Archive& ar)\
    {\
        ar.in() , ##__VA_ARGS__ ;\
    }


//usage:
struct Packet_ReqeustLogin
    : public Serializable
{
    std::string name;
    std::string password;

    SerialFormat(name, password);
};

在 Xcode5 和 VC11 中测试过,但在 VC11 中不起作用。

VC11的输出是这样的:

警告 C4002:宏“SerialSize1”的实际参数过多

我能做些什么来解决它?

【问题讨论】:

  • 欢迎来到 Stack Overflow。请尽快阅读About 页面。似乎您想编写foo(a1, a2, ..., aN) 并且您希望扩展调用名称取决于参数数量的宏?您提供的示例并不引人注目,因为您似乎只需要逗号分隔的参数,而 __VA_ARGS__ 无论如何都会给您。你能详细说明你真正想要的输出吗?你能显示几个(两个,也许三个)示例调用所需的输出吗?
  • 我不知道Macro returning the number of arguments it is given in C 是否会有所帮助。可能。
  • 我的第一个想法来自那篇文章。谢谢。
  • 我很确定Potatoswatter 在他的answer 中是正确的。您需要宏中的额外间接级别,以便将参数计数扩展为一个数字。幸运的是,它被扩展为一个可以组合的简单预处理器令牌。

标签: c++ c macros variadic-macros


【解决方案1】:

C 预处理器不是您想要做的事情的正确工具(即使您克服了这个问题)。

首先,确定你不能用 C++ 模板解决问题。

如果做不到这一点,它也需要代码生成:以某种符号表示您的类的规范并生成包含所有序列化内容的代码。

这是另一回事。您正在努力诱使宏生成包含多个项的总和:

GetSize(arg1) + GetSize(arg2) + ... + GetSize(argN)

但是你忽略了你可以有一个 N 元函数来做同样的事情:

GetSizes(arg1, arg2, ... , argN);

现在,宏不必生成多个函数调用项,其间有一个 + 运算符,而只需要生成逗号分隔的 args 列表!

您在原始程序中也过于复杂了。该程序中的printf 可以简单地实现:

$ gcc -std=c99 -Wall -pedantic test.c
$ ./a.out
1 2 3
$ cat test.c
#include <stdio.h>

#define foo(arg, ...) arg, ##__VA_ARGS__

int main()
{
  printf("%d %d %d\n", foo(1, 2, 3));
  return 0;
}

【讨论】:

  • 感谢您的友好回答,很抱歉为时已晚接受。我通过可变参数模板解决了这个问题。
【解决方案2】:

您不能将宏调用放在binder 的参数中,因为它直接使用## 运算符。

binder(_VA_NARGS(__VA_ARGS__), __VA_ARGS__)
#define binder(count, ...) arg##count(__VA_ARGS__)
=> arg##_VA_NARGS(__VA_ARGS__)(__VA_ARGS__)
=> arg_VA_NARGS(__VA_ARGS__)(__VA_ARGS__)

要对参数进行宏替换,请使用中间宏。

#define binder_impl(count, ...) arg##count(__VA_ARGS__)
#define binder(...) binder_impl( __VA_ARGS__ )

我不知道你的最终目标是什么,但这个 bug 突然出现在我身上。

【讨论】:

    【解决方案3】:

    最大的诀窍是了解## 以及__VA_ARGS__ 如何扩展。这是我用于 Linux 系统调用的示例(args 的数量 -1 ,因为第一个 arg 指的是系统调用号......请注意,它因此以 0 结尾,而不是大多数其他示例)

    #define MKFNS(fn,...) MKFN_N(fn,##__VA_ARGS__,9,8,7,6,5,4,3,2,1,0)(__VA_ARGS__)
    #define MKFN_N(fn, n0, n1, n2, n3, n4, n5, n6, n7, n8, n9, n, ...) fn##n
    
    #define syscall(...) MKFNS(syscall,##__VA_ARGS__)
    #define syscall1(n,a) _syscall1(n,(long)(a))
    #define syscall2(n,a,b) _syscall2(n,(long)(a),(long)(b))
    #define syscall3(n,a,b,c) _syscall3(n,(long)(a),(long)(b),(long)(c))
    #define syscall4(n,a,b,c,d) _syscall4(n,(long)(a),(long)(b),(long)(c),(long)(d))
    #define syscall5(n,a,b,c,d,e) _syscall5(n,(long)(a),(long)(b),(long)(c),(long)(d),(long)(e))
    

    现在我可以通过以下方式定义一个系统调用:#define open(...) syscall(__NR_open,__VA_ARGS__),当使用 3 个参数调用 open 时,它将扩展为 syscall3(5,(long)a,(long)b,(long)c)(__NR_open 的 5 间接来自 #include unistd.h)。

    【讨论】:

    • ,##__VA_ARGS__ 是非标准的,无论如何您似乎都错过了 nullary case 的实现。您已将参数计数和标识符粘贴组合到一个宏中,这是一种有效的解决方法,但与为每个任务都有一个可重用的宏完全不同。
    • null case 不需要强制转换为 long,所以直接写成 inline asm。对于 __VA_ARGS__ 至少为 1 的情况,不需要 ##,但对于 Linux 系统调用,它是可移植的,并且必须考虑零参数才能工作,如果你可以想出一个“标准”的方式来做到这一点,请让我知道,但它是相当可重用的,只需`#define args(...) MKFNS(ars,##__VA_ARGS__)`,如果总是有 1 + arg,则可以使用标准的__VA_ARGS__ 代替__VA_ARGS__
    猜你喜欢
    • 2012-02-29
    • 2020-03-31
    • 1970-01-01
    • 2019-09-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多