I want to write a generic serialization library which provides e.g. a generic save
function.
The library contains custom type traits, e.g. some_condition
:
template <typename T>
struct some_condition
{
constexpr static bool value = std::is_same<std::string, T>::value ||std::is_arithmetic<T>::value ;
};
save
's behavior is selected based on some_condition
:
template <typename T>
std::enable_if_t<some_condition<T>::value> save(const T& value)
{
std::cout << "these types will be handled in a specific way: " << value << std::endl;
}
template <typename T>
std::enable_if_t<!some_condition<T>::value> save(const T& value)
{
std::cout << "these types will be handled in another way: " << value << std::endl;
}
save
shall be customizable for user datatypes, not only via overloading, but also generically via traits.
Therefore I created trait_extension
which can be specialized for traits templates:
template <template<typename> class Trait, typename T>
struct trait_extension : Trait<T>
{
}
save
has to be modified accordingly:
template <typename T>
std::enable_if_t<trait_extension<some_condition,T>::value> save(const T& value) { ... }
template <typename T>
std::enable_if_t<!trait_extension<some_condition,T>::value> save(const T& value) { ... }
A user could now provide his own specialization of trait_extension
:
template <typename T>
struct trait_extension<some_condition, T>
{
// user specific extension: exclude floats from condition
constexpr static bool value = !std::is_floating_point<T>::value && some_condition<T>::value;
};
My question::
Is there a "better" / more elegant way to realize extensible traits?
live example
I don't think your approach is elegant at all. It could easily spiral into spaghetti code and makes it difficult to maintain or use. I would instead adopt a "policy"-based approach, similar to the std::allocator
class in the standard library. Changing behavior is a simple matter of implementing the allocator interface and providing it as a template parameter. Then everything works itself out automagically.
For one thing, in a "generic serialization" library, you need to worry not only about types, but also about localization. It could be something as simple as using ,
instead of .
or as complex as uniform capitalization. With your approach, it's not very easy to modify the stream or locale (i.e, std vs boost) back-end, but with a policy based approach it's a matter of search and replace:
serialize<int, std_locale<int>>(32.000)
serialize<int, boost_locale<int>>(32.000)
This allows you to provide a set of "defaults" in say a master locale class ala std::allocator
, and then the user can just inherit from that and change behavior for one or two types, rather than provide crazy SFINAE overloads.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With