I need feature-based design, where each feature is added to a class's bitmask FeatureList
.
However, each class should compile even if the individual features of the class are removed from the compile.
For example, instead of a class looking like this:
// Won't compile if PositionFeature isn't compiled!
#include "PositionFeature.h"
class Object
{
Object()
{
FeatureList.AddFlag(RegisteredTypeIndex<PositionFeature>);
}
};
ENGINE_REGISTER_TYPE(Object)
It should look like this:
// Should compile without PositionFeature, but do nothing!
class PositionFeature;
class Object
{
Object()
{
FeatureList.AddFlag(RegisteredTypeIndex<PositionFeature>);
}
};
ENGINE_REGISTER_TYPE(Object)
The issue stems from my current attempt of type registry design, which does not allow FeatureList to correctly search for unregistered features sharing a name with the registered feature. (Currently sees them as different features, and won't compile)
How could this compile-time flexibility be created in c++?
With that asked, this is my current registry:
#pragma once
#include <cstddef>
#include <type_traits>
#include <utility>
template <typename T>
struct tag { using type = T; };
template <typename...>
struct type_list {};
namespace List
{
// Returns the index of an element in `type_list`, or causes an error if no such element
template <typename L, typename T>
struct find_type {};
template <typename T>
struct find_type<type_list<>, T> {};
template <typename F, typename ...P, typename T>
struct find_type<type_list<F, P...>, T> : std::integral_constant<std::size_t, 1 + find_type<type_list<P...>, T>::value> {};
template <typename F, typename ...P>
struct find_type<type_list<F, P...>, F> : std::integral_constant<std::size_t, 0> {};
}
namespace StatefulList
{
namespace impl
{
template <typename Name, std::size_t Index>
struct ElemReader
{
// Hides GCC warnings
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnon-template-friend"
#endif
friend constexpr auto adl_ListElem(ElemReader<Name, Index>);
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif
};
template <typename Name, std::size_t Index, typename Value>
struct ElemWriter
{
friend constexpr auto adl_ListElem(ElemReader<Name, Index>)
{
return tag<Value>{};
}
};
constexpr void adl_ListElem() {} // A dummy ADL target
template <typename Name, std::size_t Index, typename Unique, typename = void>
struct CalcSize : std::integral_constant<std::size_t, Index> {};
template <typename Name, std::size_t Index, typename Unique>
struct CalcSize < Name, Index, Unique, decltype(void(adl_ListElem(ElemReader<Name, Index>{}))) > : CalcSize<Name, Index + 1, Unique> {};
template <typename Name, std::size_t Index, typename Unique>
using ReadElem = typename decltype(adl_ListElem(ElemReader<Name, Index>{}))::type;
template <typename Name, typename I, typename Unique>
struct ReadElemList {};
template <typename Name, std::size_t ...I, typename Unique>
struct ReadElemList<Name, std::index_sequence<I...>, Unique> { using type = type_list<ReadElem<Name, I, Unique>...>; };
}
struct DefaultUnique {};
template <typename T>
struct DefaultPushBackUnique {};
// Calculates the current list size
template <typename Name, typename Unique = DefaultUnique>
inline constexpr std::size_t size = impl::CalcSize<Name, 0, Unique>::value;
// Touch this type to append `Value` to the list
template <typename Name, typename Value, typename Unique = DefaultPushBackUnique<Value>>
using PushBack = impl::ElemWriter<Name, size<Name, Unique>, Value>;
// Returns the type previously passed to `WriteState`, or causes a SFINAE error
template <typename Name, std::size_t I, typename Unique = DefaultUnique>
using Elem = impl::ReadElem<Name, I, Unique>;
// Returns the list elements as `Meta::TypeList<...>`
template <typename Name, typename Unique = DefaultUnique>
using Elems = typename impl::ReadElemList<Name, std::make_index_sequence<size<Name, Unique>>, Unique>::type;
}
// Each tag creates a different "counter" for types
struct CounterCommonTag {};
// Returns the index of `T`, or errors out if it wasn't registered
template <typename T>
constexpr std::size_t RegisteredTypeIndex = List::find_type<StatefulList::Elems<CounterCommonTag, T>, T>::value;
// Returns the total number of registered types.
// Don't mention this in the code until finish all registrations, otherwise the value will get stuck after the first mention.
// To work around this, you can pass different types to `Unique` to force a recalculation
template <typename Unique = StatefulList::DefaultUnique>
constexpr std::size_t RegisteredTypeCount = StatefulList::size<CounterCommonTag, Unique>;
// Registers a type
#define ENGINE_REGISTER_TYPE(type_) static_assert((void(StatefulList::PushBack<CounterCommonTag, type_>{}), true));
I don't entirely understand what your code attempts to do, but I think you essentially want a type registration using stateful template metaprogramming:
#include <cstddef>
#include <type_traits>
#include <utility>
template <typename T>
struct tag {using type = T;};
template <typename...>
struct type_list {};
namespace List
{
// Returns the index of an element in `type_list`, or causes an error if no such element.
template <typename L, typename T>
struct find_type {};
template <typename T>
struct find_type<type_list<>, T> {};
template <typename F, typename ...P, typename T>
struct find_type<type_list<F, P...>, T> : std::integral_constant<std::size_t, 1 + find_type<type_list<P...>,T>::value> {};
template <typename F, typename ...P>
struct find_type<type_list<F, P...>, F> : std::integral_constant<std::size_t, 0> {};
}
namespace StatefulList
{
namespace impl
{
template <typename Name, std::size_t Index>
struct ElemReader
{
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnon-template-friend"
#endif
friend constexpr auto adl_ListElem(ElemReader<Name, Index>);
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif
};
template <typename Name, std::size_t Index, typename Value>
struct ElemWriter
{
friend constexpr auto adl_ListElem(ElemReader<Name, Index>)
{
return tag<Value>{};
}
};
constexpr void adl_ListElem() {} // A dummy ADL target.
template <typename Name, std::size_t Index, typename Unique, typename = void>
struct CalcSize : std::integral_constant<std::size_t, Index> {};
template <typename Name, std::size_t Index, typename Unique>
struct CalcSize<Name, Index, Unique, decltype(void(adl_ListElem(ElemReader<Name, Index>{})))> : CalcSize<Name, Index + 1, Unique> {};
template <typename Name, std::size_t Index, typename Unique>
using ReadElem = typename decltype(adl_ListElem(ElemReader<Name, Index>{}))::type;
template <typename Name, typename I, typename Unique>
struct ReadElemList {};
template <typename Name, std::size_t ...I, typename Unique>
struct ReadElemList<Name, std::index_sequence<I...>, Unique> {using type = type_list<ReadElem<Name, I, Unique>...>;};
}
struct DefaultUnique {};
template <typename T>
struct DefaultPushBackUnique {};
// Calculates the current list size.
template <typename Name, typename Unique = DefaultUnique>
inline constexpr std::size_t size = impl::CalcSize<Name, 0, Unique>::value;
// Touch this type to append `Value` to the list.
template <typename Name, typename Value, typename Unique = DefaultPushBackUnique<Value>>
using PushBack = impl::ElemWriter<Name, size<Name, Unique>, Value>;
// Returns the type previously passed to `WriteState`, or causes a SFINAE error.
template <typename Name, std::size_t I, typename Unique = DefaultUnique>
using Elem = impl::ReadElem<Name, I, Unique>;
// Returns the list elements as `Meta::TypeList<...>`.
template <typename Name, typename Unique = DefaultUnique>
using Elems = typename impl::ReadElemList<Name, std::make_index_sequence<size<Name, Unique>>, Unique>::type;
}
// Each such tag creates a different "counter" for types.
struct MyCommonTag {};
// Returns the index of `T`, or errors out if it wasn't registered.
template <typename T>
constexpr std::size_t MyTypeIndex = List::find_type<StatefulList::Elems<MyCommonTag, T>, T>::value;
// Returns the total number of registered types.
// Don't mention this in the code until finish all registrations, otherwise the value will get stuck after the first mention.
// To work around this, you can pass different types to `Unique` to force a recalculation.
template <typename Unique = StatefulList::DefaultUnique>
constexpr std::size_t MyTypeCount = StatefulList::size<MyCommonTag, Unique>;
// Registers a type.
#define REGISTER_MY_TYPE(type_) static_assert((void(StatefulList::PushBack<MyCommonTag, type_>{}), true));
REGISTER_MY_TYPE(int)
static_assert(MyTypeIndex<int> == 0);
REGISTER_MY_TYPE(float)
static_assert(MyTypeIndex<float> == 1);
REGISTER_MY_TYPE(char)
static_assert(MyTypeIndex<char> == 2);
static_assert(MyTypeIndex<int> == 0);
static_assert(MyTypeIndex<float> == 1);
static_assert(MyTypeIndex<char> == 2);
static_assert(MyTypeCount<> == 3);
Here types serve as your "enum constants".
This doesn't work across multiple translation units, all registrations have to be visible when needed.