Объявите несколько переменных-членов разных типов в шаблонном классе в соответствии с параметром шаблона

Можно ли определить шаблон класса таким образом, чтобы количество переменных-членов для каждого шаблонного класса зависело от параметра шаблона (т. е. std::size_t)? Я буду называть этот параметр шаблона «размером».

На данный момент я определил набор обычных классов, соответствующих размерности == 2 и размерности == 3.

Для измерения == 2 этот набор классов составляет следующее:

class Scalar2 {}; // a "grade-0" object
class Vector2 {}; // a "grade-1" object
class Bivector2 {}; // a "grade-2" object

class Multivector2
{
public:
    Multivector2(Scalar2 scal, Vector2 vec, Bivector2 bivec);
private:
    Scalar2 scal;
    Vector2 vec;
    Bivector2 bivec;
};

Для размерности == 3 мы имеем

class Scalar3 {}; // a "grade-0" object
class Vector3 {}; // a "grade-1" object
class Bivector3 {}; // a "grade-2" object
class Trivector3 {}; // a "grade-3" object

class Multivector3
{
public:
    Multivector3(Scalar3 scal, Vector3 vec, Bivector3 bivec, Trivector3 trivec);
private:
    Scalar3 scal;
    Vector3 vec;
    Bivector3 bivec;
    Trivector3 trivec;
};

Итак, для измерения n существует n+1 оцениваемых классов (от оценки 0 до оценки n), а затем один мультивекторный класс, содержащий по одной каждой отдельной оценке.

Теперь я хочу распространить это на произвольное количество измерений. Итак, по сути, я хочу что-то вроде этого:

template<std::size_t n /* dimension */, std::size_t k /* grade */>
class K_Vector {};

template<std::size_t n /* dimension */>
class Multivector
{
public:
    Multivector(K_Vector<n,0> kvec0, K_Vector<n,1> kvec1, K_Vector<n,2> kvec2, ... K_Vector<n,n> kvecn); 
private:
    K_Vector<n,0> kvec0;
    K_Vector<n,1> kvec1;
    K_Vector<n,2> kvec2;
    ...
    K_Vector<n,n> kvecn;
};

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

Насколько мне известно, не существует способа объявить несколько переменных-членов программно, например, в цикле. В любом случае это было бы не совсем идеально, поскольку присвоение имен отдельным членам было бы довольно проблематично. Итак, хранение членов внутри контейнера, такого как std::vector, звучит как шаг в правильном направлении. Проблема в том, что std::vector требует, чтобы его элементы были одинаковыми. В моем случае каждая переменная-член в Multivector имеет свой тип.

Один из вариантов, который я рассмотрел, — это получить градуированные классы из базового класса и сохранить различные компоненты внутри вектора указателей на базовый:

class K_Vector_Base {};

template<std::size_t n /* dimension */, std::size_t k /* grade */>
class K_Vector : public K_Vector_Base {};

template<std::size_t n /* dimension */>
class Multivector
{
public:
    Multivector(std::vector<std::unique_ptr<K_Vector_Base>> kvecs); 
private:
    std::vector<std::unique_ptr<K_Vector_Base>> kvecs;
};

Однако это не совсем идеально: чтобы избежать нарезки объектов, а поскольку Multivector должен владеть своими градуированными подкомпонентами, нам нужно хранить их как unique_ptrs, что в любом случае кажется пустой тратой хранения классов оценок, которые в любом случае являются легковесными по отдельности. Мультивекторы больше не смогут храниться непрерывно. Более того, этот подход потребует большого количества преобразований от базового к отдельным типам; отдельные градуированные классы слишком различны в том, как представлены их данные и как они участвуют в арифметических операциях, поэтому невозможно использовать преимущества виртуальных методов. например double dot_product(Vector, Vector), Vector dot_product(Vector, Bivector) и double dot_product(Bivector, Bivector) будут использовать разную логику и возвращать разные типы; это должны быть отдельные бесплатные функции, а не виртуальные функции.

Существуют ли какие-либо подходы, которые могли бы привести к созданию наборов классов, более похожих на размерность == 2 и размерность == 3 в конкретных случаях? (Примечание: ожидается, что классы, соответствующие определенному размерному числу, смогут сосуществовать и взаимодействовать посредством арифметических операций. Классы разных размерностей не будут совместимы друг с другом; вы можете добавить K_Vector<3,1> к K_Vector<3,2> (оба измерения 3), но вы не можете добавить K_Vector<3,1> к K_Vector<2,1>).

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

Dmytro Ovdiienko 30.07.2024 23:44
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
1
52
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы можете использовать std::tuple для хранения различных K_Vector:

#include <cstddef>
#include <tuple>
#include <utility>

template <std::size_t N /* dimension */>
class Multivector {
public:
    // the storage type:
    using m_kvecs_type = decltype([] {
        return []<std::size_t... Is>(std::index_sequence<Is...>) {
            return std::tuple<K_Vector<N, Is>...>{};
        }(std::make_index_sequence<N + 1>{});
    }());

    template <class... Args> // constructor
    Multivector(Args&&... args) : m_kvecs{std::forward<Args>(args)...} {}

    // getters:
    template <size_t I>
    auto& get() { return std::get<I>(m_kvecs); }

    template <size_t I>
    const auto& get() const { return std::get<I>(m_kvecs); }

    template <class T>
    auto& get() { return std::get<T>(m_kvecs); }
    
    template <class T>
    const auto& get() const { return std::get<T>(m_kvecs); }

private:
    m_kvecs_type m_kvecs;
};

Пример использования:

int main() {
    Multivector<2> mv(K_Vector<2, 0>{}, K_Vector<2, 1>{}, K_Vector<2, 2>{});
    
    auto& kv0 = mv.get<K_Vector<2, 0>>();  // get reference to K_Vector<2, 0>
    auto& kv2 = mv.get<2>();  // get reference to K_Vector<2, 2>
}

Демо


Как отметил Jarod42, неограниченный конструктор будет соответствовать конструктору, не являющемуся const копированием. Чтобы ограничить соответствие N + 1K_Vector, вы можете добавить дополнительный уровень, где реализация принимает пакет size_t вместо одного N:

template <class Seq> class MultivectorImpl;

template <std::size_t... Is>
class MultivectorImpl<std::index_sequence<Is...>> {
public:
    static inline constexpr size_t N = sizeof...(Is) - 1;

    MultivectorImpl(K_Vector<N, Is>... args) : m_kvecs{std::move(args)...} {}

    // getters, same as above

private:
    std::tuple<K_Vector<N, Is>...> m_kvecs;
};

template <std::size_t N>
using Multivector = MultivectorImpl<std::make_index_sequence<N + 1>>;

Демо

Обратите внимание на неограниченный конструктор с переменным числом вариантов, он будет соответствовать конструктору неконстантной копии.

Jarod42 31.07.2024 13:06

В этом случае я обычно использую дополнительные слои Демо

Jarod42 31.07.2024 13:16

@Jarod42 Это очень хороший момент. Я добавил это в ответ. Спасибо!

Ted Lyngmo 31.07.2024 13:50
decltype([] { return []<std::size_t... Is>(std::index_sequence<Is...>) { return std::tuple<K_Vector<N, Is>...>{}; }(std::make_index_sequence<N + 1>{}); }()); можно упростить до decltype([]<std::size_t... Is>(std::index_sequence<Is...>) { return std::tuple<K_Vector<N, Is>...>{}; }(std::make_index_sequence<N + 1>{}));, хотя я бы, честно говоря, рассмотрел возможность извлечения этого значения в шаблон типа, чтобы избежать лямбда-выражений в неоцененном контексте.
Patrick Roberts 31.07.2024 14:22

Другие вопросы по теме