Как динамически создавать экземпляры агрегатов с различными базовыми классами?

Мне нужно динамически создавать экземпляры класса Aggregate на C++, который наследуется от нескольких базовых классов, где комбинация базовых классов может варьироваться в зависимости от условий выполнения. Я хочу избежать предварительного заполнения всех возможных комбинаций.

Вот упрощенная версия того, чего я пытаюсь достичь

У меня есть несколько базовых классов:

class CharBase
{
    CharBase() { std::cout << "CharBase\n"; }
};

class UnsignedCharBase
{
    UnsignedCharBase() { std::cout << "UnsignedCharBase\n"; }
};

class ShortBase
{
    ShortBase() { std::cout << "ShortBase\n"; }
};

class IntBase
{
    IntBase() { std::cout << "IntBase\n"; }
};

Я использую следующий шаблон класса Aggregate для наследования от нескольких базовых классов.

template<typename... Bases>
class Aggregate : Bases…
{
    Aggregate() { std::cout << "Aggregate\n"; }
};

Моя цель — создавать экземпляры классов Aggregate по требованию на основе условий времени выполнения. Ожидаемый результат — создание экземпляра Aggregate так, как если бы я его жестко запрограммировал, например:

new Aggregate<CharBase, UnsignedCharBase, ShortBase>
new Aggregate<UnsignedCharBase, IntBase>

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

Remy Lebeau 06.08.2024 23:30

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

Kitiara 06.08.2024 23:40

давайте предположим, что вы можете, как вы планируете использовать его позже?

Iłya Bursov 06.08.2024 23:41

Путем анализа некоторых операторов из файла JSON во время выполнения.

Kitiara 06.08.2024 23:43

Я имею в виду — давайте предположим, что вы объявили такой шаблон и создали экземпляр такого класса — как вы будете его использовать? вы вызовете какие-нибудь методы, передадите их куда-нибудь?

Iłya Bursov 06.08.2024 23:44

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

Kitiara 06.08.2024 23:48

как вы определите, какие методы вы можете вызывать и каким методам передавать экземпляр такого класса?

Iłya Bursov 06.08.2024 23:50

Это похоже на работу для std::tuple.

Pete Becker 06.08.2024 23:50

@Kitiara «Я пытаюсь динамически решить, какую комбинацию базовых классов использовать во время выполнения, а затем создать экземпляр класса Aggregate, который наследуется от этих базовых классов» — Извините, но C++ просто не работает таким образом. Ближе всего к этому можно подойти примерно так: if (useCharBase && useUnsignedCharBase && useShortBase) { Aggregate<CharBase, UnsignedCharBase, ShortBase> agg; doSomethingWith(agg); } else if (useUnsignedCharBase && useIntBase) { Aggregate<UnsignedCharBase, IntBase> agg; doSomethingWith(agg); } else ... Очень быстро это становится очень некрасиво.

Remy Lebeau 06.08.2024 23:52

@Kitiara Это очень похоже на XY-проблему . Было бы полезно, если бы вы отредактировали свой вопрос, чтобы показать примеры того, как вы ожидаете использовать эти экземпляры классов. Однако в этой ситуации композиция может иметь больше смысла, чем наследование.

Remy Lebeau 06.08.2024 23:54

@IłyaBursov Опять из Json. Что вы получаете в ? Типовая безопасность? Шаблон класса Aggregate обеспечивает безопасность типов, гарантируя, что можно вызывать только допустимые методы из включенных базовых классов.

Kitiara 06.08.2024 23:58

@RemyLebeau Я бы не задавал этот вопрос, если бы хотел заняться композицией. Я не согласен называть проблемой XY Problem, когда ее сложно решить.

Kitiara 07.08.2024 00:01

@PetBecker Наверное. Возможно, шаблонный кортеж с задействованным std::index_sequence

Kitiara 07.08.2024 00:03

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

Iłya Bursov 07.08.2024 00:04

@Kitiara Я не называю это проблемой XY, потому что это сложно. Я называю это так, потому что именно это я вижу в следующем: «вы пытаетесь решить проблему X и думаете, что решение Y сработает, но вместо того, чтобы спрашивать об X, когда у вас возникают проблемы, вы спрашиваете об Y». Мы не знаем, в чем заключается ваша проблема X (какова ваша цель с этими классами), но вы, кажется, думаете, что Y (динамическое указание базовых классов во время выполнения) является решением, хотя это не может быть так, поскольку C++ не делает этого. поддержите это. Поэтому вам нужно другое решение. Если бы мы знали проблему, которую вы хотите решить, мы могли бы помочь найти решение.

Remy Lebeau 07.08.2024 00:06

@RemyLebeau Пока мы говорим, я уже добился некоторого прогресса, так что не говорите мне, что C++ не может этого сделать.

Kitiara 07.08.2024 00:08

@Китиара, ладно, неважно. Удачи в ваших усилиях.

Remy Lebeau 07.08.2024 00:11

База данных Oracle генерирует код на лету из операторов SQL, компилирует этот код и запускает его. Ваша программа также может динамически генерировать необходимый ей код, компилировать его и запускать во время выполнения.

Eljay 07.08.2024 00:51

@Eljay Я искал только решение на C++, но спасибо за идею.

Kitiara 07.08.2024 02:17

Мой комментарий касался только решения C++, используя Oracle в качестве примера программы C++, которая использует решение C++ только для нужд SQL.

Eljay 07.08.2024 12:49

@Eljay О, я вижу, я неправильно понял.

Kitiara 07.08.2024 12:51
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
21
70
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я преодолеваю ограничение в своем предыдущем ответе. Вы можете увидеть новый ответ ниже.

Новый ответ:

#include <array>
#include <iostream>
#include <memory>
#include <tuple>

class IAggregate
{
public:
    virtual ~IAggregate() = default;
};

template<typename... Bases>
class Aggregate : public IAggregate, public Bases...
{
public:
    Aggregate() { std::cout << "Aggregate\n"; }
    virtual ~Aggregate() override = default;
};

// Base classes for demonstration
class CharBase
{
public:
    CharBase() { std::cout << "CharBase display\n"; }
};

class UnsignedCharBase
{
public:
    UnsignedCharBase() { std::cout << "UnsignedCharBase display\n"; }
};

class ShortBase
{
public:
    ShortBase() { std::cout << "ShortBase display\n"; }
};

class IntBase
{
public:
    IntBase() { std::cout << "IntBase display\n"; }
};

class FloatBase
{
public:
    FloatBase() { std::cout << "FloatBase display\n"; }
};

// List of all base classes
using AllBases                 = std::tuple<CharBase, UnsignedCharBase, ShortBase, IntBase, FloatBase>;
constexpr std::size_t NumBases = std::tuple_size_v<AllBases>;

// Custom constexpr bitset class auto configured BITS_PER_WORD
template<std::size_t N, std::size_t BITS_PER_WORD>
class ConstexprBitset
{
public:
    constexpr ConstexprBitset()
        : bits {}
    {
    }

    constexpr void set(std::size_t pos)
    {
        if (pos >= N)
            throw std::out_of_range("Bit position out of range");
        bits[pos / BITS_PER_WORD] |= (1ULL << (pos % BITS_PER_WORD));
    }

    constexpr bool test(std::size_t pos) const
    {
        if (pos >= N)
            throw std::out_of_range("Bit position out of range");
        return (bits[pos / BITS_PER_WORD] & (1ULL << (pos % BITS_PER_WORD))) != 0;
    }

private:
    std::array<unsigned long long, (N + BITS_PER_WORD - 1) / BITS_PER_WORD> bits;
};

constexpr std::size_t calculateOptimalBitsPerWord(std::size_t numBases)
{
    return (numBases + 63) / 64; // Each unsigned long long covers 64 bits
}

constexpr std::size_t OptimalBitsPerWord = calculateOptimalBitsPerWord(NumBases);

template<int Mask, typename Tuple, typename Enable = void, typename... Types>
struct TypeExtractor;

template<int Mask, typename Tuple, typename... Types>
struct TypeExtractor<Mask, Tuple, std::enable_if_t<Mask == 0>, Types...>
{
    using type = Aggregate<Types...>;
};

template<int Mask, typename Tuple, typename... Types>
struct TypeExtractor<Mask, Tuple, std::enable_if_t<Mask != 0>, Types...>
{
    static constexpr ConstexprBitset<NumBases, OptimalBitsPerWord> mask = []()
    {
        ConstexprBitset<NumBases, OptimalBitsPerWord> bs;
        for (std::size_t i = 0; i < NumBases; ++i)
            if (Mask & (1 << i))
                bs.set(i);
        return bs;
    }();

    static constexpr std::size_t highestBit = []()
    {
        for (std::size_t i = NumBases; i > 0; --i)
            if (mask.test(i - 1))
                return i - 1;
        throw std::out_of_range("No bits are set");
    }();

    using BaseType = std::tuple_element_t<highestBit, Tuple>;
    using type = typename TypeExtractor<Mask & ~(1 << highestBit), Tuple, void, Types..., BaseType>::type;
};

template<int Mask>
using ExtractTypes = typename TypeExtractor<Mask, AllBases>::type;

using AggregatePtr = std::unique_ptr<IAggregate>;
template<int Mask>
AggregatePtr createAggregateInstance()
{
    return std::make_unique<ExtractTypes<Mask>>();
}

// Helper to create a map of factory functions
template<int... Masks>
constexpr auto createFactoryMap(std::integer_sequence<int, Masks...>)
{
    using FuncType = AggregatePtr (*)();
    return std::array<FuncType, sizeof...(Masks)> {{&createAggregateInstance<Masks>...}};
}

// The factory map consists of AllBases tuple
constexpr auto factoryMap = createFactoryMap(std::make_integer_sequence<int, 1 << NumBases> {});

AggregatePtr createAggregate(int mask)
{
    if (mask < factoryMap.size())
        return factoryMap[mask]();
    return nullptr;
}

int main()
{
    for (int i = 1; i <= 3; ++i)
    {
        auto instance = createAggregate(i);
        if (!instance)
            std::cout << "Invalid combination for input: " << i << "\n";
        std::cout << "\n";
    }

    return 0;
}

Прямой эфир

Старый ответ:

Вот это решение, которое мне удалось придумать. Единственное ограничение здесь — это highestSetBit. Я пытался избавиться от этого, но не смог, буду признателен, если кто-нибудь сможет это понять.

#include <array>
#include <iostream>
#include <memory>
#include <tuple>

class IAggregate
{
public:
    virtual ~IAggregate() = default;
};

template<typename... Bases>
class Aggregate : public IAggregate, public Bases...
{
public:
    Aggregate() { std::cout << "Aggregate\n"; }
    virtual ~Aggregate() override = default;
};

class CharBase
{
public:
    static constexpr int Mask = 1 << 0;
    CharBase() { std::cout << "CharBase display\n"; }
};

class UnsignedCharBase
{
public:
    static constexpr int Mask = 1 << 1;
    UnsignedCharBase() { std::cout << "UnsignedCharBase display\n"; }
};

class ShortBase
{
public:
    static constexpr int Mask = 1 << 2;
    ShortBase() { std::cout << "ShortBase display\n"; }
};

class IntBase
{
public:
    static constexpr int Mask = 1 << 3;
    IntBase() { std::cout << "IntBase display\n"; }
};

// List of all base classes
using AllBases = std::tuple<CharBase, UnsignedCharBase, ShortBase, IntBase>;

constexpr int highestSetBit(const int mask) // This is the only limitation in this design
{
    for (int i = 31; i >= 0; --i)
        if (mask & (1 << i))
            return i;
    return -1; // should never reach here for valid masks
}

template<int Mask, typename Tuple, typename Enable = void, typename... Types>
struct TypeExtractor;

template<int Mask, typename Tuple, typename... Types>
struct TypeExtractor<Mask, Tuple, std::enable_if_t<Mask == 0>, Types...>
{
    using type = Aggregate<Types...>;
};

template<int Mask, typename Tuple, typename... Types>
struct TypeExtractor<Mask, Tuple, std::enable_if_t<Mask != 0>, Types...>
{
    static constexpr int highestBit = highestSetBit(Mask);
    using BaseType                  = std::tuple_element_t<highestBit, Tuple>;
    using type                      = typename TypeExtractor<Mask & ~(1 << highestBit), Tuple, void, Types..., BaseType>::type;
};

template<int Mask>
using ExtractTypes = typename TypeExtractor<Mask, AllBases>::type;

using AggregatePtr = std::unique_ptr<IAggregate>;

template<int Mask>
AggregatePtr createAggregateInstance()
{
    return std::make_unique<ExtractTypes<Mask>>();
}

// Helper to create a map of factory functions
template<int... Masks>
constexpr auto createFactoryMap(std::integer_sequence<int, Masks...>)
{
    using FuncType = AggregatePtr (*)();
    return std::array<FuncType, sizeof...(Masks)> {{&createAggregateInstance<Masks>...}};
}

// The factory map consist of AllBases tuple
constexpr auto factoryMap = createFactoryMap(std::make_integer_sequence<int, 1 << std::tuple_size_v<AllBases>> {});

AggregatePtr createAggregate(int mask)
{
    if (mask < factoryMap.size())
        return factoryMap[mask]();
    return nullptr;
}

int main()
{
    for (int i = 1; i <= 3; ++i)
    {
        auto instance = createAggregate(i);
        if (!instance)
            std::cout << "Invalid combination for input: " << i << "\n";
        std::cout << "\n";
    }

    return 0;
}

Прямой эфир

Итак, технически вы создали экземпляр пустого IAggregate, планируете ли вы привести его к фактическому использованию?

Iłya Bursov 07.08.2024 14:38

@IłyaBursov Нет, он возвращается в IAggregate. Если вы проверите выходные данные, вы увидите, что агрегат создается вместе с базовыми классами.

Kitiara 07.08.2024 15:35

переменная instance — это указатель на IAggregate, можете ли вы показать пример ее использования?

Iłya Bursov 07.08.2024 15:38

@IłyaBursov Сам экземпляр может регистрироваться this в контейнере с идентификатором или чем-то еще. Есть бесконечные возможности.

Kitiara 07.08.2024 16:54

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

Iłya Bursov 07.08.2024 16:58
Ответ принят как подходящий

Один из способов превратить значение времени выполнения в значение компиляции — использовать std::variant.

В вашем случае я бы использовал std::tuple для сохранения результата для «необязательного» типа.

template <typename T>
std::variant<std::tuple<>,
             std::tuple<std::type_identity<T>>>
AsOptionalTypeVar(bool b)
{
    if (b) {
        return std::tuple<std::type_identity<T>>{};
    } else {
        return std::tuple<>{};
    }
}

Помощник для преобразования кортежа в Aggregate

template <typename T> struct to_aggregate;
template <typename... Ts> struct to_aggregate<std::tuple<std::type_identity<Ts>...>>
{
    using type = Aggregate<Ts...>;
};

Тогда вы можете позволить std::visit выполнить декартово произведение

std::visit(
    [](auto... bases){
        using all_base = decltype(std::tuple_cat(bases...));
        using aggregate = typename to_aggregate<all_base>::type;

        // Use aggregate type as you want
    },
    AsOptionalTypeVar<CharBase>(useCharBase),
    AsOptionalTypeVar<UnsignedCharBase>(useUnsignedCharBase),
    AsOptionalTypeVar<ShortBase>(useShortBase),
    AsOptionalTypeVar<IntBase>(useIntBase)
);

Демо

Да, похоже, хорошее решение. Решение, которое я предлагаю, заключалось в предварительном заполнении всех комбинаций автоматически, а не вручную. Однако за это придется заплатить, и то, что вы предлагаете, намного лучше, спасибо.

Kitiara 07.08.2024 16:51

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