如何检测class是否有成员变量?

How to detect if a class has member variables?

问题

我想检测 class 是否有成员变量,如果有,则静态断言失败。类似于:

struct b {
    int a;
}
static_assert(!has_member_variables<b>, "Class should not contain members"). // Error.

struct c {
    virtual void a() {}
    void other() {}
}
static_assert(!has_member_variables<c>, "Class should not contain members"). // Fine.

struct d : c {
}
static_assert(!has_member_variables<d>, "Class should not contain members"). // Fine.

struct e : b {
}
static_assert(!has_member_variables<e>, "Class should not contain members"). // Error.

struct f : c {
    char z;
}
static_assert(!has_member_variables<f>, "Class should not contain members"). // Error.

有没有办法用 SFINAE 模板实现这个?此 class 可能具有继承甚至多重继承与虚函数(尽管基 classes 中没有成员)。

动机

我有一个非常简单的设置如下:

class iFuncRtn {
    virtual Status runFunc(Data &data) = 0;
};

template <TRoutine, TSpecialDataType>
class FuncRoutineDataHelper : public iFuncRtn {
    Status runFunc(Data &data) {
        static_assert(!has_member_variables<TRoutine>, "Routines shouldnt have data members!");
        // Prepare special data for routine
        TSpecialDataType sData(data);
        runFuncImpl(sData);
}

class SpecificRtn : 
    public FuncRoutineDataHelper<SpecificRtn, MySpecialData> {
    virtual Status runFuncImpl(MySpecialData &sData) {
        // Calculate based on input 
        sData.setValue(someCalculation);
    }
};

FunctionalityRoutine 被管理,运行 以每个报价单为基础。它们是定制的,可以执行各种各样的任务,例如联系其他设备等。传入的数据可以由例程操作,并保证在每个 tick 执行时传入,直到功能完成。根据DataHelperclass传入正确类型的数据。我不想阻止未来的人错误地将数据添加到功能例程中,因为这不太可能达到他们的预期。为了强制执行此操作,我希望找到一种使用静态断言的方法。

您可以通过编译器执行空基 class 优化来解决此问题,方法是检查从 T 派生的 class 是否与空 [=] 具有相同的大小28=] 具有虚函数:

template<typename T, typename... BaseClasses>
class IsEmpty
{
    // sanity check; see the updated demo below
    static_assert(IsDerivedFrom<T, BaseClasses...>::value);

    struct NonDerived : BaseClasses... { virtual ~NonDerived() = default; };
    struct Derived : T { virtual ~Derived() = default; };

public:
    inline static constexpr bool value = (sizeof(NonDerived) == sizeof(Derived));
};

这应该适用于单继承和多继承。但是,当使用多重继承时,有必要列出所有基数 classes,像这样:

static_assert(IsEmpty<Derived, Base1, Base2, Base3>::value);

显然,此解决方案排除了 final classes.

Here's the updated demo.

Here's the original demo.(不适用于多重继承)

您必须以某种方式标记 classes。选择一种您喜欢的方式,属性 或某种类型的带有枚举的整数成员。任何制作 sub-classes 的人都必须遵循您的约定才能使其正常工作。

这里的所有其他答案都是这个的一些变体。

任何使用 sizeof 的答案都不能保证这将在平台、编译器甚至同一平台和编译器上的 classes 之间工作,因为很容易将新成员放入默认值class 成员对齐,其中 sizeof 的大小很容易与子 class.

相同

背景:

正如您的代码和问题中所述,所有这些都只是普通和基本的 C 和 C++ 代码,并且在编译时完全解决。编译器会告诉你一个成员是否存在。编译后,它是一堆高效的、无名的机器代码,本身没有任何提示或帮助。

您为函数或数据成员使用的任何名称都会在编译后有效地消失,正如您所知并在那里看到的那样,并且无法通过名称查找任何成员。每个数据成员仅通过其相对于 class 或结构顶部的数字偏移量而为人所知。

像 .Net、Java 和其他系统是为反射而设计的,它是通过名称记住 class 成员的能力,你可以在运行时找到它们,当你编程是 运行.

C++ 中的模板,除非在诸如 .Net 之类的东西上使用混合模式 C++,它们也会在编译时全部解析,并且名称也会全部消失,因此模板本身不会给您带来任何好处。

像Objective-C这样的语言也被写成在缺少某些类型的特殊成员时不一定会失败,类似于你所问的,但在幕后它使用了大量的支持代码和运行时管理来独立跟踪,其中实际函数本身及其代码仍然未知,并依赖其他代码告诉它们成员是否存在或不会在 null 成员上失败。


在纯 C 或 C++ 中,您只需要制作自己的系统,并准确地动态跟踪什么在做什么。您可以创建枚举、列表或名称字符串的字典。这是通常所做的,你只需要给自己留下提示。 class 不能按照定义为未来子 class 提供隐式可见性的方式编译,如果 RTTI.

不使用某种形式

出于这个原因,将类型成员放在 class 上很常见,这可能是一个简单的枚举。我不会指望尺寸或任何可能依赖于平台的东西。