Как правильно объявить шаблон, вводя тип функции в качестве параметра (например, std:: function)

Я узнал, что его нетривиально иметь чистый синтаксис, например, в:

std::function<int(float, bool)>

Если я объявляю функцию как:

template <class RetType, class... Args>
class function {};

Это был бы обычный синтаксис для определения функций templated types:

function<int,float,bool> f;

Но он работает со странным трюком с частичной специализированной специализацией

template <class> class function; // #1

template <class RV, class... Args>
class function<RV(Args...)> {} // #2

Почему? Почему мне нужно предоставить шаблон пустой общей форме с пустым параметром типа (# 1) или иначе он просто не будет компилировать

Ответы

Ответ 1

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

Если я правильно понимаю, вы просто хотите написать вторую версию, не указав основной шаблон. Но подумайте о том, как аргументы шаблона сопоставляются с параметрами шаблона:

template <class RV, class Arg1, class... Args>
class function<RV(Arg1, Args...)> {}

function<int(float,bool)>; //1
function<int, float, bool>; //2

Опция 1 - это то, что вы хотите написать, но обратите внимание, что вы передаете один тип функции в шаблон, где параметры являются параметрами двух типов и пакетом параметров типа. Другими словами, запись этого без основного шаблона означает, что ваши аргументы шаблона не обязательно будут соответствовать параметрам шаблона. Опция 2 соответствует параметрам шаблона, но не соответствует специализации.

Это имеет меньшее значение, если у вас есть более чем одна специализация:

template <class RV, class Arg1, class... Args>
class function<RV(Arg1, Args...)> {}

template <class T, class RV, class Arg1, class... Args>
class function<RV (T::*) (Arg1, Args...)> {}

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

Ответ 2

Вы должны иметь в виду, что int (float, bool) - это тип. Точнее, это функция типа, которая принимает два параметра типа float и bool и возвращает int. "

Поскольку это тип, очевидно, что шаблон должен иметь параметр один в том месте, где вы хотите использовать синтаксис int (float, bool).

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

template <class T>
struct CallForwarder
{
  std::function<T> forward(std::function<T> f)
  {
    std::cout << "Forwarding!\n";
    return f;
  }
};

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

Ответ 3

Вы действительно можете это сделать:

template <typename T>
class X { };

X<int(char)> x;

И внутри определения X вы можете создать std::function<T>, как вы бы создали std::function<int(char)>. Проблема здесь в том, что вы не можете (по крайней мере легко) получить доступ к типу возврата и типу параметров аргумента (int и char здесь).

Используя "трюк", вы можете получить к ним доступ без проблем - "просто" делает ваш код более чистым.

Ответ 4

"Почему мне нужно предоставить шаблон пустой общей форме с пустым параметром типа"

Потому что вы хотите явное различие типов между типами возврата и типами аргументов, с аналогичной ясностью синтаксического сахара. В С++ такой синтаксис не доступен для первой версии template class. Вы должны специализироваться на этом, чтобы отличить его.

После прочтения вашего Q я просмотрел заголовочный файл <functional>. Даже std::function делает тот же трюк:

// <functional>  (header file taken from g++ Ubuntu)

template<typename _Signature>
class function;

// ...

 /**
   *  @brief Primary class template for std::function.
   *  @ingroup functors
   *
   *  Polymorphic function wrapper.
   */
template<typename _Res, typename... _ArgTypes>
class function<_Res(_ArgTypes...)>

Как вы видите в своих комментариях, они рассматривают специализированную версию как "первичный" класс. Следовательно, этот трюк исходит от самого стандартного заголовка!