как правильно объявить шаблон, принимающий тип функции в качестве параметра (например, 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...)>
как вы видите в своих комментариях, они рассматривают специализированную версию как "основной" класс. Следовательно, этот трюк исходит из самого стандартного заголовка!