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

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

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

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

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

это был бы обычный синтаксис для определения шаблонных типов функции:

function<int,float,bool> f;

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

template <class> class function; // #1

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

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

4 ответов


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

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

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

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


вы должны иметь в виду, что 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 делает).


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

template <typename T>
class X { };

X<int(char)> x;

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

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


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

потому что вы хотите явное дифференцирование типов среди "возвращаемого типа" и "типов аргументов" с аналогичной ясностью синтаксического сахара. В C++ такой синтаксис не доступен для первой версии 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...)>

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