Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extensible type traits in C++

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

like image 727
m.s. Avatar asked May 17 '16 10:05

m.s.


1 Answers

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.

like image 195
user6363174 Avatar answered Nov 04 '22 01:11

user6363174