Расширяемые черты типов в С++

Я хочу написать общую библиотеку сериализации, которая обеспечивает, например, общая функция save. Библиотека содержит пользовательские типы, например. 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 поведение выбирается на основе 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 должен настраиваться для пользовательских типов данных не только при перегрузке, но и в общих чертах через признаки. Поэтому я создал trait_extension, который может быть специализирован для шаблонов шаблонов:

template <template<typename> class Trait, typename T>
struct trait_extension : Trait<T>
{
}

save должен быть соответствующим образом изменен:

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) { ... }

Теперь пользователь может предоставить свою специализацию 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;
};

Мой вопрос::

Есть ли "лучший" /более элегантный способ реализовать расширяемые черты?

живой пример

Ответ 1

Я не думаю, что ваш подход очень изящный. Он может легко прокручиваться в код спагетти и затрудняет его поддержание или использование. Вместо этого я бы принял подход, основанный на "политике", аналогичный классу std::allocator в стандартной библиотеке. Изменение поведения - это простой вопрос реализации интерфейса распределителя и предоставления его в качестве параметра шаблона. Затем все работает автоматически.

Во-первых, в библиотеке "generic serialization" вам нужно беспокоиться не только о типах, но и о локализации. Это может быть что-то простое, чем использование , вместо . или столь же сложное, как равномерная капитализация. С вашим подходом не так просто изменить поток или языковой стандарт (то есть, std vs boost), но с политическим подходом это вопрос поиска и замены:

serialize<int, std_locale<int>>(32.000)
serialize<int, boost_locale<int>>(32.000)

Это позволяет вам предоставить набор "значений по умолчанию", например, класс мастер-локали ala std::allocator, а затем пользователь может просто наследовать от этого и изменять поведение для одного или двух типов, а не предоставлять сумасшедшие перегрузки SFINAE.