I need to dynamically create instances of an Aggregate
class in C++ which inherits from multiple base classes, where the combination of base classes can vary based on runtime conditions. I want to avoid pre-populating all possible combinations.
Here’s a simplified version of what I’m trying to achieve
I have several base classes:
class CharBase
{
CharBase() { std::cout << "CharBasen"; }
};
class UnsignedCharBase
{
UnsignedCharBase() { std::cout << "UnsignedCharBasen"; }
};
class ShortBase
{
ShortBase() { std::cout << "ShortBasen"; }
};
class IntBase
{
IntBase() { std::cout << "IntBasen"; }
};
I use the following Aggregate
class template to inherit from multiple base classes
template<typename... Bases>
class Aggregate : Bases…
{
Aggregate() { std::cout << "Aggregaten"; }
};
My goal is to create instances of the Aggregate
classes on demand based on runtime conditions. The expected result is to create an Aggregate
instance as if I hardcoded it, for example:
new Aggregate<CharBase, UnsignedCharBase, ShortBase>
new Aggregate<UnsignedCharBase, IntBase>
21
One way to turn runtime value to compile value is to use std::variant
.
For your case, I would use std::tuple
to store the result for “optional” type.
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<>{};
}
}
An Helper trait to transform a tuple into Aggregate
template <typename T> struct to_aggregate;
template <typename... Ts> struct to_aggregate<std::tuple<std::type_identity<Ts>...>>
{
using type = Aggregate<Ts...>;
};
Then you might let std::visit
does the Cartesian product
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)
);
Demo
4
I overcome the limitation in my previous answer. You can see the new answer below.
New Answer:
#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 << "Aggregaten"; }
virtual ~Aggregate() override = default;
};
// Base classes for demonstration
class CharBase
{
public:
CharBase() { std::cout << "CharBase displayn"; }
};
class UnsignedCharBase
{
public:
UnsignedCharBase() { std::cout << "UnsignedCharBase displayn"; }
};
class ShortBase
{
public:
ShortBase() { std::cout << "ShortBase displayn"; }
};
class IntBase
{
public:
IntBase() { std::cout << "IntBase displayn"; }
};
class FloatBase
{
public:
FloatBase() { std::cout << "FloatBase displayn"; }
};
// 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;
}
Live
Old Answer:
Here it is the solution i managed to come up with. The only limitation here is the highestSetBit
. I tried to get rid of that but i couldn’t, it is appreciated if someone can figure that out.
#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 << "Aggregaten"; }
virtual ~Aggregate() override = default;
};
class CharBase
{
public:
static constexpr int Mask = 1 << 0;
CharBase() { std::cout << "CharBase displayn"; }
};
class UnsignedCharBase
{
public:
static constexpr int Mask = 1 << 1;
UnsignedCharBase() { std::cout << "UnsignedCharBase displayn"; }
};
class ShortBase
{
public:
static constexpr int Mask = 1 << 2;
ShortBase() { std::cout << "ShortBase displayn"; }
};
class IntBase
{
public:
static constexpr int Mask = 1 << 3;
IntBase() { std::cout << "IntBase displayn"; }
};
// 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;
}
Live
7