Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to detect if a class has member variables?

Problem

I would like to detect if a class has member variables and fail a static assert if they do. Something like:

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.

Is there a way to achieve this with SFINAE template? This class may have inheritance or even multiple inheritance with virtual functions (no members in the base classes though).

Motivation

I have a pretty simple setup as follows:

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

The FunctionalityRoutines are managed and run on a per tick basis. They are customized and can perform a wide variety of tasks such as contacting other devices etc. The data that is passed in can be manipulated by the routine and is guaranteed to be passed in on each tick execution until the functionality is finished. The right type of data is passed in based on the DataHelper class. I wan't to discourage future people from mistakenly adding data to the functionality routines as it is very unlikely to do what they expect. To force this, I was hoping to find a way with static assert.

like image 665
Fantastic Mr Fox Avatar asked Sep 06 '18 15:09

Fantastic Mr Fox


1 Answers

You can solve this by depending on the compiler doing empty base class optimizations, by checking if a class derived from your T has the same size as an empty class with virtual functions:

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

This should work with both single and multiple inheritance. However, when using multiple inheritance, it's necessary to list all base classes, like that:

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

Obviously, this solution rules out final classes.

Here's the updated demo.

Here's the original demo. (doesn't work with multiple inheritance)

like image 188
joe_chip Avatar answered Sep 28 '22 12:09

joe_chip