Подтвердить что ты не робот

Параметр шаблона шаблона шаблона С++, который соответствует любым параметрам

Мне было интересно, можно ли написать функцию шаблона, которая может принимать любой другой произвольный шаблон в качестве параметра и правильно соответствовать имени шаблона (т.е. не только результирующий класс). Я знаю, что это работает:

template<template<typename ...> class TemplateT, typename... TemplateP>
void f(const TemplateT<TemplateP...>& param);

Которое будет соответствовать, например, для f(std::vector<int>()) или f(std::list<int>()), но не будет работать для f(std::array<int, 3>()), так как вторым параметром является size_t и нет типа.

Теперь я думаю, что можно было сделать что-то сумасшедшее, как:

template<template<typename ...> class TemplateT, size... Sizes, typename... TemplateP>
void f(const TemplateT<Sizes..., TemplateP...>& param);

Надеясь, что компилятор будет правильно выводить эллипсис TemplateP или Sizes, чтобы быть пустым. Но не только это уродливо, но и будет работать только для шаблонов, которые принимают либо типы, либо параметры size_t. Он по-прежнему не будет соответствовать произвольным шаблонам, например, с параметрами bool.

То же самое касается подхода с перегрузкой:

template<template<typename ...> class TemplateT, typename... TemplateP>
void f(const TemplateT<TemplateP...>& param);

template<template<typename ...> class TemplateT, size... Sizes>
void f(const TemplateT<Sizes...>& param);

Кроме того, такой подход не будет работать, если мы хотим смешивать size_t и typenames. Итак, что требовалось бы, чтобы соответствовать чему-либо, было бы что-то вроде этого, где нет никаких ограничений на то, что разрешено в многоточии:

template<template<...> class TemplateT, ... Anything>
void f(const TemplateT<Anything...>& param);

Этот синтаксис не работает, но может быть, есть другой синтаксис, чтобы определить что-то вроде этого?

Это в основном меня интересует, что возможно на языке, считая, что на самом деле может быть полезно для него, если у вас разные шаблоны, где первый параметр всегда исправлен, и вы хотели бы изменить его на основе типа возврата и держите все остальное. Что-то вроде этого:

template<
    template<typename ValueT, ...> class TemplateT,
    ... Anything,
    typename ValueT,
    typename ResultT = decltype(some_operation_on_value_t(std::declval<ValueT>())>
TemplateT<ResultT, Anything...> f(const TemplateT<ValueT, Anything...>& in);

Итак, любой способ сделать эту работу полностью универсальным способом с использованием сопоставления шаблонов?

Это не просто мысленный эксперимент, так как прецедент для этого, где я застрял, заключался в создании чистых функциональных примитивов, которые работают на контейнерах, и неявно создадут неизменяемые контейнеры результатов. Если контейнер результатов имеет другой тип данных, нам нужно знать тип, в котором работает контейнер, поэтому единственным требованием для любого контейнера было бы то, что первым параметром шаблона должен быть тип ввода, чтобы его можно было заменить другим тип вывода в результате, но код должен быть не обратим внимания на любой аргумент шаблона, следующий за ним, и не должен заботиться о том, является ли он типом или значением.

4b9b3361

Ответ 1

Ваша интересная конструкция имеет два уровня с вариативными шаблонами.

  • Список параметров внешнего вариационного шаблона TemplateP и Sizes для шаблона функции
  • Внутренний пакет параметров в качестве параметров шаблона вашего шаблона шаблона TemplateT, шаблон класса

Во-первых, давайте посмотрим на внутренний класс TemplateT: почему оператор многоточия не соответствует чему-то вроде TemplateT< int, 2 >? Ну, стандарт определяет вариационные шаблоны в §14.5.3 как

template<class ... Types> struct Tuple { };
template<T ...Values> struct Tuple2 { };

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

Tuple < 0 >    error;  // error, 0 is not a type!
Tuple < T, 0 > error2; // T is a type, but zero is not!
Tuple2< T >    error3; // error, T is not a value
Tuple2< T, 0 > error4; // error, T is not a value

все искажены. Кроме того, невозможно вернуться к чему-то вроде

template<class ... Types, size_t ...Sizes> struct Tuple { };

поскольку стандартные состояния в §14.1.11:

Если шаблон-шаблон шаблона первичного класса или шаблон псевдонима является пакетом параметров шаблона, он должен быть последним параметром шаблона. Пакет параметров шаблона функции не должен соблюдаться другим параметром шаблона, если только этот параметр шаблона не может быть выведен из списка параметров-типа шаблона функции или имеет аргумент по умолчанию (14.8.2).

Другими словами, для шаблонов классов в определении может отображаться только параметр один. Поэтому указанное выше (двойное) -вариадическое определение класса неверно. Поскольку внутренний класс всегда нуждается в такой комбинации, невозможно написать что-то общее, как вы задумали.


Что можно спасти? Для шаблона внешней функции некоторые осколки могут быть объединены, но вам это не понравится. Пока второй пакет параметров можно вывести из первого, могут появиться два пакета параметров (в шаблоне функции). Следовательно, такая функция, как

template < typename... Args, size_t... N > void g(const std::array< Args, N > &...arr);
g(std::array< double, 3 >(), std::array< int, 5>());

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

Ответ 2

У вас должен быть metafunction, который перепроверяет тип контейнера. Поскольку вы не можете просто заменить первый параметр шаблона:

vector<int, allocator<int> > input;
vector<double, allocator<int> > just_replaced;
vector<double, allocator<double> > properly_rebound;

Итак, просто напишите такой метафунд для известного набора контейнеров.

template<class Container, class NewValue> class rebinder;

// example for vectors with standard allocator
template<class V, class T> class rebinder< std::vector<V>, T > {
public:
  typedef std::vector<T> type;
};
// example for lists with arbitrary allocator
template<class V, class A, class T> class rebinder< std::list<V,A>, T > {
  typedef typename A::template rebind<T>::other AT; // rebind the allocator
public:
  typedef std::list<T,AT> type; // rebind the list
};
// example for arrays
template<class V, size_t N> class rebinder< std::array<V,N>, T > {
public:
  typedef std::array<T,N> type;
};

Правила перезаписи могут различаться для разных контейнеров.

Также вам может потребоваться метафункция, которая извлекает тип значения из произвольного контейнера, а не только std-совместимый (typedef *unspecified* value_type)

template<class Container> class get_value_type {
public:
  typedef typename Container::value_type type; // common implementation
};
template<class X> class get_value_type< YourTrickyContainer<X> > {
  ......
public:
  typedef YZ type;
};

Ответ 3

Было бы здорово, если бы у нас было такое, что позволило бы нам написать черту is_same_template на ветру.
До тех пор мы специализируемся на всем пути.