Частичная специализация шаблона класса на основе условия?

#c #templates #c 17

Вопрос:

Я вообще не вижу, как это можно было бы реализовать.

У меня есть неполный шаблон класса, используемый для «переопределения» некоторых методов.
Это один из рабочих примеров:

 template<typename...>
struct make_completion;

struct ServiceTraits
{
    using mode_type = ModeType;
    using warning_type = Waring;
    using error_type = Error;
    using iteration_result_type = IterationResu<

    using steering_type = Steering;

    using sync_tag_t = decltype(asio::use_future);
};

template<typename SignatureT>
struct make_completion<ServiceTraits, SignatureT>
{
    template<typename CompletionToken>
    auto operator()(ServiceTraits, CompletionToken amp;amp;completionToken)
    {
        using result_type = asio::async_result<std::decay_t<CompletionToken>, SignatureT>;
        using completion_handler = typename result_type::completion_handler_type;

        completion_handler handler{ std::forward<CompletionToken>(completionToken) };
        result_type result{ handler };

        return Completion<result_type, completion_handler>{ std::move(result), std::move(handler) };
    }
};

 

Я хочу собрать traits класс с помощью наследования, поэтому, например, у меня было бы несколько классов признаков, а затем произвольно объединить их.

Например:

 template<typename ResultT, typename HandlerT>
struct completion
{
   using result_type = ResultT;
   using handler_type = HandlerT;
   result_type resu<
   handler_type handler;
};

// this would be inherited from
struct asio_traits
{
   using sync_tag_t = decltype(asio::use_future);
};
 

Проблема в том, что я не могу использовать что-то подобное std::enable_if в специализации.
Это не будет компилироваться:

 template<typename DerivedTraits,
         typename SignatureT,
         typename = std::enable_if_t<std::is_base_of_v<asio_traits, DerivedTraits>>>
struct eos::method::make_completion<DerivedTraits, SignatureT>
{
   // impl here...
};
 

Можно ли как-то использовать условие для специализации шаблона класса?

Комментарии:

1. Обычно вы используете аргумент шаблона фиктивного типа в основном шаблоне, template<typename Enable = void> а в ограниченных частичных специализациях вы передадите результат std::enable_if_t в качестве аргумента шаблона: struct eos::method::make_completion<std::enable_if_t< ... >> ... . Однако за пакетом параметров шаблона не может следовать дополнительный параметр шаблона, поэтому это добавляет некоторую сложность. Я бы предложил использовать смешивания для комбинированного признака и предоставить этот параметр шаблона одного типа шаблону make_completion класса.

2. @dfrib спасибо вам за ваши предложения. Если вы можете показать пример, я бы счел это законным ответом. На данный момент я просто отделился asio_traits как async_traits имя зависимого типа. Это достаточно просто

Ответ №1:

Что-то в этом роде, возможно:

 template <typename...>
struct make_completion_tag;

template <typename Tag, typename...>
struct make_completion_impl;

template <typename... Ts>
struct make_completion : public make_completion_impl<
    typename make_completion_tag<Ts...>::type,
    Ts...>
{};

struct asio_traits_based_tag;
template <typename DerivedTraits, typename SignatureT>
struct make_completion_impl<asio_traits_based_tag, DerivedTraits, SignatureT> {
  // Your specialization here
};
 

Теперь осталось только вернуть make_completion_tag::type правильный тег. Например.

 template <typename DerivedTraits, typename SignatureT>
struct make_completion_tag<DerivedTraits, SignatureT> {
  using type = std::conditional_t<
      std::is_base_of_v<asio_traits, DerivedTraits>,
      asio_traits_based_tag, void>;
};
 

ИЗМЕНИТЬ: немного другой подход, который предоставляет внешнюю точку настройки:

 template <typename Tag, typename...>
struct make_completion_impl;

void make_completion_selector(...);

template <typename... Ts>
struct make_completion : public make_completion_impl<
    decltype(make_completion_selector(static_cast<Ts*>(nullptr)...)),
    Ts...>
{};
 

Теперь у пользователя есть два варианта. Если типы позволяют, они могут специализироваться make_completion напрямую:

 template <AnotherType>
struct make_completion<SpecificType, AnotherType> {
  // Your specialization here
};
 

В более сложном случае они могут объявить соответствующим образом ограниченную перегрузку make_completion_selector возврата типа тега и специализироваться make_completion_impl на этом теге:

 struct asio_traits_based_tag;
template <typename DerivedTraits, typename SignatureT>
struct make_completion_impl<asio_traits_based_tag, DerivedTraits, SignatureT> {
  // Your specialization here
};

template <typename DerivedTraits, typename SignatureT>
std::enable_if_t<std::is_base_of_v<asio_traits, DerivedTraits>,
                 asio_traits_based_tag>
make_completion_selector(DerivedTraits*, SignatureT*);
 

Комментарии:

1. В специализации нет ничего особенного make_completion_tag<DerivedTraits, SignatureT> , поскольку DerivedTraits и SignatureT они так же универсальны, как и были. Поэтому , хотя вы и сделали так, чтобы это сработало asio_traits , вы сделали так, чтобы это не сработало для всех других специализаций. Компилятор не сможет вывести между asio_traits и some_other_traits «специализациями», если кто-то захочет их добавить. Единственное, что отличает make_completion_tag специализации, — это ряд аргументов шаблона. Что в моем случае всегда равно 2. Тем не менее, спасибо за усилия.

2. Если вам нужно больше тегов, вы можете добавить в них больше предложений std::conditional . Или у вас может быть куча перегруженных вспомогательных функций, возвращающих тег, выбранный с помощью SFINAE, и сделайте это using type=decltype(tag_helper((DerivedTraits*)nullptr, (SignatureT*)nullptr));

3. Зачем мне это нужно? Весь смысл этого подхода состоит в том, чтобы иметь специализации, которые не зависят друг от друга, и базовому шаблону не нужно ничего знать о специализациях. При добавлении тегов std::conditional это намерение будет нарушено, так как вам придется объединить все возможные теги вместе, введя зависимости от их заголовков. Цель здесь-удобство для пользователя: нужно было бы написать только одну специализацию, никогда не меняя реализацию.

4. Не могли бы вы попросить пользователя объявить перегрузку определенного имени функции, ограниченного SFINAE? Например. struct my_tag; template <typename D, typename S> std::enable_if_t<some_condition, my_tag> make_completion_selector(D*, S*); Затем вы могли бы использовать decltype(make_completion_selector((Ts*)nullptr ...) в качестве тега для выбора make_completion_impl<my_tag, ...> специализации.

5. Суть этого вопроса заключается в том, что я не могу использовать SFINAE при специализации шаблона класса. SFINAE предполагает условие в общем шаблоне. Поэтому, если бы я предположил, что определенный класс признаков должен быть основой параметра шаблона, можно было бы использовать наследование. Это довольно запутанно и, как мне кажется, очень трудно поддерживать. Теперь я использую более простое решение — вложенные типы. Подход с SFINAE потребует определения a Type в базовом шаблоне и неявной проверки того, является ли он основой другого Type или того же по умолчанию… Вы могли бы попробовать это