Как разработать шаблонный класс с переключаемыми переменными-членами во время компиляции в C ++? - PullRequest
0 голосов
/ 18 марта 2020

| ■ Определение Probelm ____________________

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

  1. It может отключить ненужные переменные-члены в зависимости от ситуации.
  2. Каким переменным он будет обладать, определяется во время компиляции .

Я как-то создал тот, который определяет список его членов " до " времени компиляции , используя макросов и перечислитель flags . см. ниже:

▼ TraitSwitch.h

#pragma once
// Macros to switch-off source codes themselves.
#define ON                 1
#define OFF                0

#define TRI_AREA_INFO      ON
#define TRI_CENTROID_INFO  ON
#define TRI_NORMAL_INFO    OFF // When the normal vector info is unnecessary.
...

▼ TriangleTraits.h

#pragma once
#include <cstdint>
#include "TraitSwitch.h"

enum TriangleTrait : uint8_t
{
    NONE          = 0,   // 0000 0000

#if (TRI_AREA_INFO == ON)
    AREA          = 1,   // 0000 0001
#endif

#if (TRI_CENTROID_INFO == ON)
    CENTROID      = 2,   // 0000 0010
#endif

#if (TRI_NORMAL_INFO == ON) //        | Inactive Preprocessor Block
    NORMAL_VECTOR = 4,   // 0000 0100 |
#endif
    ... // more traits

    ALL           = 255  // 1111 1111
}
// Need some additional overloaded bitwise-operators (&, |, |=, etc ...)

▼ Triangle.h

#pragma once
#include "TriangleTraits.h"

class Triangle
{
public:
    Triangle() {}
    ~Triangle() {}

#if (TRI_AREA_INFO == ON)
    double area;
#endif

#if (TRI_CENTROID_INFO == ON)
    double centroid[3]; // x, y, z
#endif

#if (TRI_NORMAL_INFO == ON) //   | Inactive Preprocessor Block
    double normal[3]; // x, y, z |
#endif
    ...

    TriangleTrait alreadyComputed; // To avoid redundant works.
    void ComputeTraits(TriangleTrait _requested)
    {
        if (((_requested & TriangleTrait::AREA) != 0) 
            && ((alreadyComputed & _requested) == 0))
        {
            this->ComputeArea();
            alreadyComputed |= TriangleTrait::AREA;
        }
        ... // do the same things for centroid, normal
    }

private:
    void ComputeArea();
    void ComputeCentroid();
    void ComputeNormal();
    ...
}

затем C ++ IntelliSense на объекте может отображать: this

▼ main . cpp

#include <iostream>
#include "Triangle.h"

int main(void)
{
    Triangle tri;
    tri.ComputeTraits(TriangleTrait::AREA | TriangleTrait::CENTROID);

    std::cout << "area : " << tri.area << "m²" << std::endl;
    std::cout << "centroid : (" 
        << tri.centroid[0] << "," 
        << tri.centroid[1] << "," 
        << tri.centroid[2] << ")" << std::endl;
    ...
}

Во-первых Triangle.h выглядит довольно некрасиво, и даже если выглядит хорошо, этот метод определяет членов класса " before" время компиляции , в любом случае.


| ■ Краткое изложение вопросов ____________________

"Как создать класс шаблона с переключаемыми элементами , которые определяются во время компиляции ."

Вот то, что я хочу:

▼ main. cpp

...

int main(void)
{
    Triangle<__MACRO_DEFINED_TRAIT_SWITCH(AREA)> tri1; // This owns area info only
    tri1.area;

    Triangle<__MACRO_DEFINED_TRAIT_SWITCH(AREA | CENTROID)> tri2; // This owns area & centroid info
    tri2.area;
    tri2.centroid;

    Triangle<__MACRO_DEFINED_TRAIT_SWITCH(AREA | NORMAL)> tri3; // This owns area & normal vector info
    tri3.area;
    tri3.normal;
    ...

    Triangle<__MACRO_DEFINED_TRAIT_SWITCH(AREA | CENTROID | NORMAL)> tri4; // This owns area & centroid & normal vector info
    tri4.area;
    tri4.centroid;
    tri4.normal;
    ...
}

Я предполагаю, используя шаблоны вместе взятые с макросами методом диспетчеризации тегов , может быть?) будет делать именно то, что я хочу, но понятия не имею.

Ответы [ 2 ]

2 голосов
/ 20 марта 2020

После нескольких дней бедствия я нашел способ. Поэтому я сам отвечу на свой вопрос.

Душа была слишком простой. Просто используйте множественное наследование с variadi c templates . См. Ниже:

▼ TriangleTraits.h

struct AREA
{
    double area;
};

struct CENTROID
{
    double centroid[3];
};

struct NORMAL
{
    double normal[3];
};
... // more traits

// Multiple Inheritances with variadic template
template<class... Traits>
struct TriangleWithTraits : Traits... 
{
};

▼ основной. cpp

#include "TriangleTraits.h" // Just include this one is enough

int main()
{
    TriangleWithTraits<AREA> tri1;
    tri1.area;

    TriangleWithTraits<AREA, CENTROID> tri2;
    tri2.area;
    tri2.centroid;

    TriangleWithTraits<AREA, NORMAL> tri3;
    tri3.area;
    tri3.normal;

    TriangleWithTraits<AREA, CENTROID, NORMAL> tri4;
    tri4.area;
    tri4.normal;
    tri4.centroid;
...
}
0 голосов
/ 19 марта 2020

Невозможно использовать препроцессор для разбора параметра шаблона, препроцессор не знает о C ++.

Но есть std::conditional и есть оптимизация пустой базы.

Таким образом, вы можете иметь базы с частями класса данных и std::conditional для выбора между ними и пустой базой:

struct EmptyBase {};

struct BaseWithChar
{
    char a;
};

struct BaseWithLongLong
{
    long long b;
    long long c;
};

template<int Flags>
struct A
    : std::conditional<(Flags & 1), BaseWithChar, EmptyBase>::type
    , std::conditional<(Flags & 2), BaseWithLongLong, EmptyBase>::type
{
};

int main() {
    std::cout << sizeof(A<1>) << std::endl;
    std::cout << sizeof(A<2>) << std::endl;
    std::cout << sizeof(A<3>) << std::endl;
    return 0;
}

А с более поздними стандартами C ++ есть std::coditional_t и [[no_unique_address]]:

using namespace std;

struct EmptyMember {};

template<int Flags>
struct A
{
    [[no_unique_address]] typename std::conditional_t<(Flags & 1), char, EmptyMember> a;
    [[no_unique_address]] typename std::conditional_t<(Flags & 2), long long, EmptyMember> b;
    [[no_unique_address]] typename std::conditional_t<(Flags & 2), long long, EmptyMember> c;
};

int main() {
    std::cout << sizeof(A<1>) << std::endl;
    std::cout << sizeof(A<2>) << std::endl;
    std::cout << sizeof(A<3>) << std::endl;
    return 0;
}

Но [[no_unique_address]] требует C ++ 20, и без [[no_unique_address]] пустой член будет использовать некоторое пространство. Похоже, он не работает с Visual C ++, даже с предварительным просмотром 2019 года. Я отправил feebdack .

В более раннем C ++, когда у вас нет std::conditional или boost::conditional, вы можете легко реализовать свой собственный, это просто шаблон с истинным и ложная специализация. См. «Возможную реализацию» на странице cppreference

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...