【问题标题】:Is it possible to write a macro existenceof(S,f) or hasfield(S,f) in C++11?是否可以在 C++11 中编写宏 existsof(S,f) 或 hasfield(S,f)?
【发布时间】:2013-09-26 08:58:47
【问题描述】:

我相当熟悉不涉及宏魔法的标准元编程解决方案(例如C++11 ways of finding if a type has member function or supports operator?)。但是,我有一个涉及以下便利宏的用例(当然,对于 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();

我自己设法想出了以下适用于 Clang 的 hasfield 实现......

#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);                                        \
    }()

...但不幸的是,这似乎是由于a bug in 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&lt;c&gt;().f)

【问题讨论】:

  • 根本不是答案,只是提示:即使m 是非静态数据成员,您也可以将sizeof(std::declval&lt;T&gt;().m) 简化为sizeof(T::m)(与decltype 类似)。
  • 我可以想象typeid().name()__cxa_demangle 在序列化时会派上用场。只是一个提示

标签: c++ c++11 macros template-meta-programming


【解决方案1】:

通过从 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)。

【讨论】:

  • 对我来说看起来不错,但 GCC 4.6.3 仍然不接受它。这可能是一个 GCC 错误(它可能已在 4.7 或 4.8 中修复),但我想知道是否有办法修复它以便它可以在 4.6 中工作......
  • @Quuxplusone 看起来它应该很简单,可以解决。我现在不在安装 GCC 4.6 的系统上,但我会看看。
  • @Quuxplusone 你的版本和 hvd 的炸弹在 GCC 4.7.2 上出现内部编译器错误。
  • 仅供参考,我想我现在有一个工作版本;当我将它插入整个系统时,它给了我错误“模板参数 1 无效”,但我认为该错误与 HAS_FIELD 无关。我将在今天晚些时候发布代码。 @SchighSchagh 很糟糕。有人知道它是否在 4.8 中修复了吗?
  • 让它工作的诀窍是将decltype(f(fieldname))替换为char[sizeof f(fieldname)]; GCC 4.6 在 decltype 中的错误比在 sizeof 中的错误更多。
【解决方案2】:

感谢@hvd the clever idea of looking for either a local variable or an inherited member! 这是我最终在 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() -&gt; decltype(data_member),但它对 auto member_func() -&gt; char[sizeof data_member] 很满意。

【讨论】:

  • 您可以简单地将fieldname 设置为非常大且尺寸奇特,例如0x31337(注意:奇数很重要)。可能比1 更安全。那么,作为一个未使用的变量,它应该被丢弃并且永远不会被创建......
  • @Yakk 我考虑过这一点,但不想让编译器在堆栈上留下一个大对象,如果它没有出于任何原因得到优化。
  • 把它放在一个本地块中,确保它没有被初始化。然后更糟糕的是,它是宏“主体”本地的两次堆栈指针移动(向上和向下)。
  • @Yakk 是的,如果这些动作导致 %rsp 离开当前线程的分配堆栈[让我们谈谈巨大的多线程应用程序,这意味着很小的堆栈],并且进程接收到一个信号,而 %rsp 是垃圾价值,可怕的事情发生。恕我直言,最好不要创建巨大的对象,如果可以避免它(在这种情况下可以)。但这也没什么大不了的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-12-20
  • 1970-01-01
  • 1970-01-01
  • 2018-05-31
  • 2013-05-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多