Как определить, есть ли в классе переменные-члены? - PullRequest
0 голосов
/ 06 сентября 2018

Задача

Я хотел бы определить, есть ли в классе переменные-члены, и не выполнить статическое утверждение, если они есть. Что-то вроде:

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 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 управляются и работают на основе тактов. Они настроены и могут выполнять широкий спектр задач, таких как установление связи с другими устройствами и т. Д. Передаваемыми данными можно манипулировать с помощью подпрограммы, и она гарантированно будет передаваться при каждом выполнении тиков до тех пор, пока функциональность не будет завершена. Правильный тип данных передается на основе класса DataHelper. Я не хотел бы отговаривать будущих людей от ошибочного добавления данных в функциональные подпрограммы, поскольку вряд ли они будут делать то, что ожидают. Чтобы заставить это, я надеялся найти способ со статическим утверждением.

Ответы [ 2 ]

0 голосов
/ 06 сентября 2018

Вам нужно будет так или иначе пометить классы.Выберите способ, которым вам удобно, свойство или какой-то тип целочисленного члена с перечислением.Кто бы ни делал подклассы, должен будет следовать вашему соглашению, чтобы заставить это работать.

Все остальные ответы здесь будут некоторым вариантом этого.

Любой ответ, который использует sizeof, не может гарантировать, что это будет работать между платформами, компиляторами или даже классами на той же платформе и компиляторе,из-за легкой возможности размещения нового члена в выравнивании членов класса по умолчанию, где размеры sizeof могут легко оказаться одинаковыми для подкласса.


Фон:

Как указано в вашем коде и вопросе, все это просто простой и базовый код C и C ++, и полностью решается во время компиляции.Компилятор скажет вам, существует ли член или нет.После компиляции он представляет собой набор эффективного, безымянного машинного кода без подсказок или помощи для такого рода вещей.

Любое имя, которое вы используете для функции или члена данных, фактически исчезает, как вы это знаете иПосмотрите его там после компиляции, и нет способа найти какой-либо элемент по имени.Каждый элемент данных известен только его числовым смещением от вершины класса или структуры.

Системы, подобные .Net, Java и другим, предназначены для отражения, то есть способности запоминать членов класса по имени,где вы можете найти их во время выполнения, когда ваша программа запущена.

Шаблоны в C ++, если только смешанный режим C ++ на чем-то похожем на .Net, также не разрешен во время компиляции, и имена также все пропадут,поэтому шаблоны сами по себе ничего не покупают.

Языки, подобные Objective-C, также написаны так, что они не обязательно перестают работать, если отсутствуют определенные типы специальных членов, аналогично тому, что вы спрашиваете, но под прикрытием его использованиемного поддержки кода и управления во время выполнения для независимого отслеживания, когда сама функция и ее код все еще не осведомлены и полагаются на другой код, чтобы сообщить им, существует ли член или нет на нулевом члене.


В чистом C или C ++ вам нужно будет просто создать свою собственную систему и быть грамотнымЯ про динамическое отслеживание, что делает что.Вы можете создавать перечисления, списки или словари строк имен.Это то, что обычно делается, вы просто должны оставить подсказки для себя.Класс не может быть скомпилирован способом, который дает неявную видимость будущим подклассам по определению, без использования какой-либо формы, если RTTI.

По этой самой причине обычно помещают член типа в класс, что можетбыть простым перечислением.Я бы не стал рассчитывать на размеры или что-то, что могло бы зависеть от платформы.

0 голосов
/ 06 сентября 2018

Вы можете решить эту проблему в зависимости от того, выполняет ли компилятор пустую оптимизацию базового класса, проверяя, имеет ли класс, производный от вашего T, такой же размер, как у пустого класса с виртуальными функциями:

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

Это должно работать как с одиночным, так и с множественным наследованием. Однако при использовании множественного наследования необходимо перечислить все базовые классы, например:

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

Очевидно, что это решение исключает final классов.

Вот обновленная демка.

Вот оригинальная демонстрация. (не работает с множественным наследованием)

...