7

我相当熟悉不涉及宏魔法的标准元编程解决方案(例如,查找类型是否具有成员函数或支持运算符的 C++11 方法? )。但是,我有一个涉及以下便利宏的用例(当然,对 StackOverflow 进行了极大的简化,但想象一下这是用于序列化之类的)......

#define START(type) do { typedef type current; const char typeName[] = #type
#define OUTPUT(fieldname) \
    printf("type of %s.%s is %s\n", #type, #fieldname, \
        std::is_same<decltype(std::declval<current>().fieldname),int> ? "int" : "string")
#define END() } while (0)


struct Foo { int i; char *j; char *k; };
struct Bar { char *x; int y; };

START(Foo);
  OUTPUT(i);  // type of Foo.i is int
  OUTPUT(j);  // type of Foo.j is string
  OUTPUT(k);  // type of Foo.k is string
END();
START(Bar);
  OUTPUT(x);  // type of Bar.x is string
  OUTPUT(y);  // type of Bar.y is int
END();

但是现在假设有人出现并向我们的模式添加了一种新的数据成员:字段对(x, xLength)。我们想像这样改变我们的便利宏......

#define START(obj) do { const auto& current = (obj)
#define OUTPUT(fieldname) \
    printf("type of %s.%s is %s\n", #type, #fieldname, \
        std::is_same<decltype(std::declval<current>().fieldname),int> ? "int" :
        hasfield(current, fieldname##Length) ? "Pascal string" : "C string")
#define END() } while (0)


struct Baz { char *x, *y, *z; int xLength, zLength; };

START(Baz);
  OUTPUT(x);  // type of Baz.x is Pascal string
  OUTPUT(y);  // type of Baz.y is C string
  OUTPUT(z);  // type of Baz.z is Pascal string
END();

就我自己而言,我设法提出了以下hasfield适用于 Clang 的实现......

#define hasfield(classtype, fieldname)                                        \
    []() {                                                                    \
        struct X {                                                            \
            template<class T, int=sizeof(&T::fieldname)> static constexpr bool f(T*){ return true; } \
            static constexpr bool f(...) { return false; }                    \
        }; return X::f((classtype*)0);                                        \
    }()

...但不幸的是,这似乎是由于Clang 中的错误;根据 C++11 标准,本地类X不允许有模板成员。实际上,此代码无法使用 GCC 编译。

所以我很难过:是否可以在 C++11 中定义OUTPUT宏以使其能够满足我的要求?

绝对约束:不改变Baz. fieldname无需提前进行硬编码。

Nice-to-haves:hasfield(c,f)也可以在其他上下文中使用的宏(而不是将代码直接缠绕到OUTPUT宏中)。没有假设offsetof(c,fLength)==offsetof(c,f)+sizeof(std::declval<c>().f)

4

2 回答 2

3

current通过继承和依赖阴影,可以使其在某些对您可能或可能无关紧要的限制下工作:声明一个局部fieldname变量,创建一个从您正在检查的类型派生的局部类,以及在成员函数内部,检查是否fieldname仍然引用局部变量。如果是,则不fieldname存在任何成员。

#include <utility>
#include <stdio.h>

#define START(type) do { typedef type current; const char typeName[] = #type
#define HASMEMBER(fieldname) \
    []() -> bool { \
        struct HASMEMBER1 { } fieldname; \
        struct HASMEMBER2 : current { \
             static char TEST1(HASMEMBER1&); \
             static char (&TEST1(...))[2]; \
             auto TEST2() -> decltype(TEST1(fieldname)); \
        }; \
        return sizeof(std::declval<HASMEMBER2>().TEST2()) == 2; \
    }()
#define OUTPUT(fieldname) \
    printf("type of %s.%s is %s\n", typeName, #fieldname, \
        std::is_same<decltype(current::fieldname),int>::value ? "int" : \
        HASMEMBER(fieldname##Length) ? "Pascal string" : "C string")
#define END() } while (0)

struct Foo { int i; char *j; char *k; };
struct Bar { char *x; int y; };
struct Baz { char *x, *y, *z; int xLength, zLength; };

int main()
{
START(Foo);
  OUTPUT(i);  // type of Foo.i is int
  OUTPUT(j);  // type of Foo.j is C string
  OUTPUT(k);  // type of Foo.k is C string
END();
START(Bar);
  OUTPUT(x);  // type of Bar.x is C string
  OUTPUT(y);  // type of Bar.y is int
END();
START(Baz);
  OUTPUT(x);  // type of Baz.x is Pascal string
  OUTPUT(y);  // type of Baz.y is C string
  OUTPUT(z);  // type of Baz.z is Pascal string
END();
}

编辑为在 GCC 4.6.3 上工作。它仍然被 GCC 4.8.1 和 clang 3.3 接受,并且也应该与 GCC 4.7.3 一起使用(但不是 4.7.2)。

于 2013-09-20T23:26:23.247 回答
1

感谢@hvd寻找局部变量或继承成员的聪明想法!这是我最终在 GCC 4.6.3 上工作的确切代码:

#include <utility>
#include <stdio.h>

#define START(type) do { typedef type current; const char typeName[] = #type
#define HASMEMBER(fieldname) \
    []()->bool { \
        char fieldname; \
        struct HASMEMBER2 : current { \
             auto TEST2() -> char[sizeof(fieldname)]; \
        }; \
        return sizeof(std::declval<HASMEMBER2>().TEST2()) != 1; \
    }()
#define OUTPUT(fieldname) \
    printf("type of %s.%s is %s\n", typeName, #fieldname, \
        std::is_same<decltype(current::fieldname),int>::value ? "int" : \
        HASMEMBER(fieldname##Length) ? "Pascal string" : "C string")
#define END() } while (0)


struct Foo { int i; char *j; char *k; };
struct Bar { char *x; int y; };
struct Baz { char *x, *y, *z; int xLength, zLength; };

int main()
{
START(Foo);
  OUTPUT(i);  // type of Foo.i is int
  OUTPUT(j);  // type of Foo.j is C string
  OUTPUT(k);  // type of Foo.k is C string
END();
START(Bar);
  OUTPUT(x);  // type of Bar.x is C string
  OUTPUT(y);  // type of Bar.y is int
END();
START(Baz);
  OUTPUT(x);  // type of Baz.x is Pascal string
  OUTPUT(y);  // type of Baz.y is C string
  OUTPUT(z);  // type of Baz.z is Pascal string
END();
}

请注意,这种方法无法区分 1 字节长char xLength和缺少xLength. 对于我的申请,这是可以接受的;我所有的xLength字段都是 4 个字节长或不存在。

请注意,这HASMEMBER仅适用于非私有成员变量;它不应该用于测试私有成员(duh)或成员函数。同样,这对于我的应用程序是可以接受的。

作为记录,GCC 4.6.3 存在使其拒绝编译auto member_func() -> decltype(data_member)的错误,但它对auto member_func() -> char[sizeof data_member].

于 2013-09-23T20:27:41.017 回答