Reputation: 133577
I have a specific situation in which I would like to prepare some runtime structures at compile time without the need to duplicate code.
I have two structs that I use to register at compile time some types for a compiler I wrote:
using TypeID = u8;
template<typename T, typename TYPE_ID, TYPE_ID I>
struct TypeHelper
{
static constexpr TYPE_ID value = std::integral_constant<TYPE_ID, I>::value;
};
template<typename T> struct Type : TypeHelper<T, u8, __COUNTER__> { static_assert(!std::is_same<T,T>::value, "Must specialize for type!"); };
These are used in a config header with a macro that specialize Type<T>
for multiple types I require:
using type_size = unsigned char;
#define GET_NTH_MACRO(_1,_2,_3, NAME,...) NAME
#define REGISTER_TYPE(...) GET_NTH_MACRO(__VA_ARGS__, REGISTER_TYPE3, REGISTER_TYPE2, REGISTER_TYPE1)(__VA_ARGS__)
#define REGISTER_TYPE1(_TYPE_) REGISTER_TYPE2(_TYPE_, _TYPE_)
#define REGISTER_TYPE2(_TYPE_,_NAME_) \
constexpr TypeID TYPE_##_NAME_ = __COUNTER__; \
template<> struct Type<_TYPE_> : TypeHelper<_TYPE_, type_size, TYPE_##_NAME_> { \
static constexpr const char* name = #_NAME_; \
};
REGISTER_TYPE(void)
REGISTER_TYPE(s64)
REGISTER_TYPE(s32)
so that these expand to
constexpr TypeID TYPE_void = 2;
template<> struct Type<void> : TypeHelper<void, type_size, TYPE_void> { static constexpr const char* name = "void"; };
constexpr TypeID TYPE_s64 = 3;
template<> struct Type<s64> : TypeHelper<s64, type_size, TYPE_s64> { static constexpr const char* name = "s64"; };
constexpr TypeID TYPE_s32 = 4;
template<> struct Type<s32> : TypeHelper<s32, type_size, TYPE_s32> { static constexpr const char* name = "s32"; };
This is working fine but the compiler also requires some runtime information about these types, so in addition to this I must define some auxiliary functions like
static TypeID typeForIdent(const std::string& name);
static const char* nameForType(TypeID type);
static void mapTypeName(TypeID type, const std::string& name);
inline bool isSigned(TypeID type)
{
return type == Type<s8>::value || type == Type<s16>::value ||
type == Type<s32>::value || type == Type<s64>::value;
}
and similar functions.
These functions must work without template arguments, so that TypeID
must be a normal argument. But I'm required to initialize such data in a separate part of code, eg:
mapTypeName(Type<s32>::value, "s32");
which uses a static std::unordered_map<TypeID, std::string>
. Of course this also implies that I must maintain twice the code when most of the information is already available at compile time through the types defines.
I was wondering if there's some obscure trick which I'm missing which could coalesce these so that REGISTER_TYPE
macro also registers the runtime information. I haven't come with anything yet but maybe there's a clever way to manage this.
Upvotes: 2
Views: 711
Reputation: 13988
I've seen you use c++ extension __COUNTER__
and as such maybe you will find interesting string literal gcc and clang extension which would allow you to bind string literal expressing registered type directly to your Type
(without additional identification number):
#include <iostream>
template <class Char, Char... Cs>
struct string_literal {
static Char str[sizeof...(Cs) + 1];
};
template <class Char, Char... Cs>
Char string_literal<Char, Cs...>::str[sizeof...(Cs) + 1] = { Cs..., '\0' };
template <class Char, Char... Cs>
constexpr string_literal<Char, Cs...> operator ""_sl() {
return {};
}
template <class T, class SL>
struct TypeHelper { };
template <class T>
struct Type;
template <class A, class B>
auto getType(TypeHelper<A, B>) {
return B{};
}
#define REGISTER(TYPE) template <> \
struct Type<TYPE>: TypeHelper<TYPE, decltype(#TYPE##_sl)> { };
struct X{};
REGISTER(void)
REGISTER(int)
REGISTER(X)
int main(){
std::cout << decltype(getType(Type<void>{}))::str << std::endl;
}
Output:
void
Upvotes: 1
Reputation: 93274
If you are not particularly concerned with the performance of registering your run-time data into the map, you could simply use an inline
function that returns a reference to a static
map instance, and generate "dummy" registrar
instances in your REGISTER_TYPE
macro that fill the map in their constructor.
inline auto& registration_map()
{
static std::unordered_map<int, std::string> m;
return m;
}
struct registrar
{
registrar(int id, std::string s)
{
registration_map()[id] = std::move(s);
}
};
template <typename T>
struct TypeHelper { };
#define CAT3_IMPL(a, b, c) a ## b ## c
#define CAT3(a, b, c) CAT3_IMPL(a, b, c)
#define REGISTER_TYPE(id, type) \
template<> struct TypeHelper<type> { }; \
[[maybe_unused]] registrar CAT3(unused_registrar_, __LINE__, type) {id, #type};
REGISTER_TYPE(0, int)
REGISTER_TYPE(1, float)
REGISTER_TYPE(2, double)
int main()
{
assert(registration_map()[0] == "int");
assert(registration_map()[1] == "float");
assert(registration_map()[2] == "double");
}
Notes:
You may get repeated registrations if the same REGISTER_TYPE
is included in multiple translation units.
CAT3(unused_registrar_, __LINE__, type)
is used to generate an unique name that will not collide with other REGISTER_TYPE
expansions.
Upvotes: 2