I have a template class I would like to specialize for many different types. Something like:
#include <type_traits>
#include <string>
#include <string_view>
// To allow static assert for invalid branches
template <typename T>
inline constexpr bool invalid = false;
template <typename T>
struct TypeMeta
{
using type = T;
using intermediate_type = T;
static consteval auto marshal_string() {static_assert(invalid<T>, "Not implemented for type.");}
static constexpr auto convert(intermediate_type &&value) {return type{value};}
static constexpr bool needs_conversion = !std::is_same_v<type, intermediate_type>;
};
I would like to be able to override parts of this class like so:
struct TypeMeta<std::string>
{
using intermediate_type = const char *;
static consteval std::string_view marshal_string() {return "s";}
};
Then use this further for something like:
template <typename T>
T extract_from(void *data)
{
using meta_t = TypeMeta<T>;
if constexpr (meta_t::needs_conversion) {
typename meta_t::intermediate_type tmp;
tmp = *(static_cast<meta_t::intermediate_type *>(data));
return meta_t::convert(tmp);
} else {
return *(static_cast<meta_t::type *>(data));
}
}
Obviously, this doesn't work for std::string because I haven't redefined type, needs_conversion, and convert in my specialization.
Now, I can get around this pretty easily by having a base class to inherit from using CRTP. Something like:
template <typename T>
struct TypeMeta;
template <typename T>
struct TypeMetaBase
{
using type = T;
using intermediate_type = T;
static consteval auto marshal_string() {static_assert(invalid<T>, "Not implemented for type.");}
static constexpr type convert(intermediate_type &&value) {return type{value};}
static constexpr bool needs_conversion = !std::is_same_v<typename TypeMeta<T>::type, typename TypeMeta<T>::intermediate_type>;
};
template <typename T>
struct TypeMeta : public TypeMetaBase<T>{};
template <>
struct TypeMeta<std::string> : public TypeMetaBase<std::string>
{
using intermediate_type = const char *;
static consteval std::string_view marshal_string() {return "s";}
};
Which works just fine. I think it's slightly bad that you have to remember to inherit from the base for every type, but that's probably unavoidable.
The real problem, however, comes when you want to specialize just the one or two functions you actually need to from the parent. I want to do this:
template <>
consteval std::string_view TypeMeta<int>::marshal_string() {return "i"};
But this is not allowed:
<source>:xx:yy: error: no member function 'marshal_string' declared in 'TypeMeta<int>'
xx | consteval auto TypeMeta<int>::marshal_string() {return "i";}
I can get around this by duplicating the method in my template:
template <typename T>
struct TypeMeta : public TypeMetaBase<T>
{
static consteval auto marshal_string() {static_assert(invalid<T>, "Not implemented for type.");}
};
Which means that specialized structs like the std::string one will fall back to using TypeMetaBase's method if it's not defined, while structs that only have some specialized methods like the int one will use TypeMeta's method for ones that aren't specialized.
This means I have to duplicate the code unnecessarily, however. I don't like that. It can easily get out of sync, and it's unnecessary bloat to boot.
Is there any way to avoid having to duplicate methods while allowing minimal extra typing? I suppose I could just demand that any specialization declare its own struct, but there are many different types, most of which only require a single overridden method, and I would prefer to keep it as concise as possible.
One extra idea might be to use a macro for extra struct declarations to avoid the boilerplate, but I would prefer to avoid using macros if possible.
I basically just want to get a nice C++ solution which doesn't involve abusing the preprocessor or copy-pasting code.
You might split each component in its own type_traits:
And then just provide specialization of individual traits:
Demo