Специализирующийся шаблон для контейнера типа T - PullRequest
4 голосов
/ 10 марта 2020

Учитывая, что у меня есть настройка шаблона, чтобы сделать что-то для типа, такого как ...

template<typename T>
class SimpleTemplate
{
private:
  T m_obj;
public:
  void operator()() { m_obj.DoSomething(); }
};

И я хочу обработать случай, когда у меня есть коллекция типа T таким же образом. В настоящее время у меня есть настройка шаблона, например, для вектора ...

template<typename T>
class SimpleTemplate<std::vector<T>>
{
private:
  std::vector<T> m_collection;
public:
  void operator()()
  {
    for (auto&& obj : m_collection) obj.DoSomething();
  }
};

Теперь я хочу также поддерживать наборы, unordered_sets и так далее. Я мог бы написать шаблон для каждой коллекции, но я чувствую, что это должна быть идеальная работа для шаблона, только я не могу понять, как он должен быть написан, или даже если это может быть? Могу ли я сделать что-то вроде template<typename C<T>>?

Ответы [ 2 ]

4 голосов
/ 10 марта 2020

Как уже упоминал Geoffroy, вы можете использовать черту, чтобы определить, можно ли повторить T. Затем вы используете это, чтобы выбрать правильную специализацию.

Итак, начните с черты is_iterable, показанной Jarod42 здесь :

// Code by Jarod42 (https://stackoverflow.com/a/29634934).
#include <iterator>
#include <type_traits>

namespace detail
{
    // To allow ADL with custom begin/end
    using std::begin;
    using std::end;

    template <typename T>
    auto is_iterable_impl(int)
    -> decltype (
        begin(std::declval<T&>()) != end(std::declval<T&>()), // begin/end and operator !=
        void(), // Handle evil operator ,
        ++std::declval<decltype(begin(std::declval<T&>()))&>(), // operator ++
        void(*begin(std::declval<T&>())), // operator*
        std::true_type{});

    template <typename T>
    std::false_type is_iterable_impl(...);

}

template <typename T>
using is_iterable = decltype(detail::is_iterable_impl<T>(0));

Это дает вам is_iterable<T> черта, которая наследуется от std::true_type или std::false_type. Теперь используйте это с SFINAE для создания двух специализаций:

template <class T, bool = is_iterable<T>::value>
class SimpleTemplate;

template <class T>
class SimpleTemplate<T, false> {
  private:
    T m_obj;

  public:
    SimpleTemplate (T obj) : m_obj(std::move(obj)) { }

    void operator() () { m_obj.DoSomething(); }
};

template <class T>
class SimpleTemplate<T, true> {
  private:
    T m_collection;

  public:
    SimpleTemplate (T obj) : m_collection(std::move(obj)) { }

    void operator() () {
      for (auto && obj : m_collection) { obj.DoSomething(); }
    }
};

Поскольку обе частичные специализации являются взаимоисключающими для любого заданного T, вы не получите никаких ошибок в отношении неоднозначности.

Редактировать: Изменен 2-й аргумент шаблона в bool вместо класса. Это упрощает его полную специализацию в случае нежелательного поведения по умолчанию.

Например, для std::string, для которого is_iterable имеет значение true, просто выполните следующее. Обратите внимание, что я добавил конструкторы в SimpleTemplate, иначе я не смог получить полную специализацию для наследования конструктора базового класса.

template <>
class SimpleTemplate<std::string, true>
    : public SimpleTemplate<std::string, false> {
  // Inherit constructor.
  using base = SimpleTemplate<std::string, false>;
  using base::base;
};
1 голос
/ 10 марта 2020

Теперь я хочу также поддерживать наборы, unordered_sets и так далее. Я мог бы написать шаблон для каждой коллекции, но я чувствую, что это должна быть идеальная работа для шаблона, только я не могу понять, как он должен быть написан, или даже если это может быть

Может быть, вы можете использовать параметр шаблона-шаблона

template <template <typename...> class C, typename... Ts>
class SimpleTemplate<C<Ts...>>
{
private:
  C<Ts...> m_collection;
public:
  void operator()()
  {
    for (auto&& obj : m_collection) obj.DoSomething();
  }
};

Это должно перехватывать std::(unordered_)(multi)set, std::vector, std::deque, et c.

К сожалению, не перехватывает std::array, потому что второй параметр шаблона - это значение, а не тип.

...