C++理解模板的确切语义,尤其是函数参数

C++ Understanding the exact semantics of templates, esp. with functional parameters

本文关键字:语义 尤其是 函数 参数 C++      更新时间:2023-10-16

当模板只是对类型进行参数化时,我很乐意使用它们。然而,我现在开始在更复杂的应用程序中使用它们(特别是,对函数进行参数化,如在C++模板中:typename和函数映射到int),并且基本上已经减少为反复试验(请注意,在上面提到的问题中,我无法编译我的代码)。

  • template关键字(或在模板定义之外使用的typename关键字)的确切语义是什么,尤其是当您希望函数作为参数时
  • 如何定义将函数作为参数的模板
  • 定义后,如何实例化和使用该模板

我将接受第一个完整的答案,其中包括这两个,一个可编译的例子,以及对魔术背后发生了什么的清晰、完整的解释?


澄清:我了解如何使用typename。我的问题是:模板定义的确切语义是什么,尤其是当应用于可调用参数时如何定义和实例化一个以函数(或函子或可调用的东西)为模板参数的类

模板和typename关键字的确切语义是什么,尤其是当您希望函数作为参数时?

template引入了模板声明(也引入了显式实例化和专门化的声明,但这超出了本问题的范围)。后面是模板参数列表,位于尖括号<>中。

在该列表中,typenameclass引入了一个类型参数;三种参数中的一种,其他是非类型(值)参数和模板参数。

如何定义将函数作为参数的模板?

最简单的方法是使用类型参数,它可以是任何可以像函数一样调用的类型;例如

template <typename F, typename... Args>
void call(F && f, Args &&... args) {f(std::forward<Args>(args)...);}

定义后,如何实例化和使用该模板?

如果是函数模板,只需使用适当类型的参数调用函数即可。模板参数将从中推导出来:

void f1(int a, double b);   // function
struct fn {
void operator()(std::string);
};
call(f1, 42, 1.5);
call(fn{}, "Hello");
call([](std::string s){std::cout << s}, "Lambdan");

对于类模板,您必须指定函数类型,这可能有点混乱:

template <typename F>
struct thing {
F f;
};
thing<void(*)(int,double)> thing1 {f1};
thing<fn> thing2 {fn{}};
// Lambdas are problematic since the type has no name
//thing<???> thing3 {[]{std::cout << "Lambdan";}};

通过使用工厂函数模板来推导函数类型并实例化类模板,可以避免混乱:

template <typename F>
thing<F> make_thing(F && f) {
return thing<F>(std::forward<F>(f));
}
auto thing1 = make_thing(f1);
auto thing2 = make_thing(fn{});
auto thing3 = make_thing([]{std::cout << "Lambdan";});

下面的一些示例(注释)代码展示了如何在模板中使用函数。如果希望第一个模板适用于任何数量的函数参数(使用可变模板),则可以将typename Parameter替换为typename... Parameter,将p替换为p...

#include <functional> // for std::function
#include <iostream>
// example functions
void func(int i)
{
std::cout << "Integer: " << i << ".n";
}
int gunc(int i)
{
int result = i+1;
std::cout << "+Integer:" << result << ".n";
return result;
}
// general non-restrictive template
template<typename Function, typename Parameter>
void call_twice(Function f, Parameter p)
{
f(p);
f(p);
}
// Restrict signature to void(int), but the return value can be anything: it will be ignored
void call_thrice(std::function<void(int)> f, int p)
{
f(p);
f(p);
f(p);
}
// Restrict signature to int(int), to exclude func
void call_four_times(std::function<int(int)> f, int p)
{
f(p);
f(p);
f(p);
f(p);
}
int main()
{
call_twice(func, 1); // instantiates void call_twice(void (*)(int), int)
call_twice(gunc, 1); // instantiated void call_twice(int (*)(int), int)
// Note I do not write the explicit types of func and gunc in the above comments, they're not important!
call_thrice(func, 10); // converts func to a std::function<void(int)>
call_thrice(gunc, 10); // return value can be ignored, so int(int) is convertible to void(int)
//call_four_times(func, 100); // will fail to compile: cannot convert a function of signature void(int) to one with signature int(int)
call_four_times(gunc, 100); // converts gunc to std::function<int(int)>
}

现场演示。


模板声明的基本形式启动形式的任何内容

template<typename T>
template<class T> // same as above
template<int N> // explicitly typed template parameter, need not be an int
template<typename... variadic_template_args>

当然还有以上组合的表单,包括嵌套模板类型等等。只需记住变量模板参数必须排在最后。

模板顾名思义:创建一个类的蓝图,该类具有在模板定义中表达的某些功能。当您实际调用模板函数(来自另一个实例化的模板函数或非模板函数)时,或者对于类模板,当您声明类型的对象时,就会发生实例化,例如std::vector:

std::vector<double> v; // instantiate the class template std::vector for type double

这与将函数作为参数的模板没有什么不同。请注意,函数在传递给另一个函数时会衰减为函数指针。


例如,一个函数作为模板参数的类模板:

#include <iostream>
#include <type_traits> // for std::result_of
template<typename Function, Function f>
struct A
{
using function_type = Function;
template<typename... ArgTypes>
typename std::result_of<Function(ArgTypes...)>::type call(ArgTypes... args)
{
return f(args...);
}
};
void func(int i) { std::cout << "Integer: " << i << ".n"; }
int main()
{
A<decltype(&func), func> a;
a.call(42);
}

现场演示。注意decltype中额外的&,因为函数只有在传递给函数时才会衰减为函数指针,而不是在用作模板参数时。注意std::result_of之前的"额外"typename:这是必需的,因为它的typetypedef是一个依赖名称。这些时候和其他时候你需要使用templatetypename关键字,这些都在哪里以及为什么我必须放"template"answers"typename"关键字?

使用lambdas 更容易

template <typename F>
void herp(F function)
{
function("Hello from herp");
}

int main()
{
const auto deDerp = [] (const std::string& msg)
{
std::cout << msg << "n";
};
herp(deDerp);
}