C++理解模板的确切语义,尤其是函数参数
C++ Understanding the exact semantics of templates, esp. with functional parameters
当模板只是对类型进行参数化时,我很乐意使用它们。然而,我现在开始在更复杂的应用程序中使用它们(特别是,对函数进行参数化,如在C++模板中:typename和函数映射到int),并且基本上已经减少为反复试验(请注意,在上面提到的问题中,我无法编译我的代码)。
template
关键字(或在模板定义之外使用的typename
关键字)的确切语义是什么,尤其是当您希望函数作为参数时- 如何定义将函数作为参数的模板
- 定义后,如何实例化和使用该模板
我将接受第一个完整的答案,其中包括这两个,一个可编译的例子,以及对魔术背后发生了什么的清晰、完整的解释?
澄清:我了解如何使用typename
。我的问题是:模板定义的确切语义是什么,尤其是当应用于可调用参数时和如何定义和实例化一个以函数(或函子或可调用的东西)为模板参数的类
模板和typename关键字的确切语义是什么,尤其是当您希望函数作为参数时?
template
引入了模板声明(也引入了显式实例化和专门化的声明,但这超出了本问题的范围)。后面是模板参数列表,位于尖括号<>
中。
在该列表中,typename
或class
引入了一个类型参数;三种参数中的一种,其他是非类型(值)参数和模板参数。
如何定义将函数作为参数的模板?
最简单的方法是使用类型参数,它可以是任何可以像函数一样调用的类型;例如
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
:这是必需的,因为它的type
typedef是一个依赖名称。这些时候和其他时候你需要使用template
和typename
关键字,这些都在哪里以及为什么我必须放"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);
}
- 在C++17中,引用const字符串的语义应该是什么
- 在C++中使用移动语义的正确方法是什么?
- " sizeof "操作员在编程中真的很重要吗,尤其是在构建大型应用程序时?
- 函数作为变量的语义是什么 C++.
- testb $1, %al 的语义是什么
- QWebEngineView立即崩溃,尤其是在滚动后 - Qt5.8
- C++什么是价值语义类?我看到类似"This component implements a fully value-semantic container class"类描述
- 模板化代码的语法和语义是什么C++
- C++语言功能可简化命名类型(尤其是在函数声明中)
- 如何在Qt中创建"编辑"菜单,尤其是MacOSX
- 既不是句法也不语义的误差
- 将迭代器转换为指针,尤其是在end()处
- C++理解模板的确切语义,尤其是函数参数
- 有没有一种可靠的方法可以在C++中强制线程停止?(尤其是独立的)
- 定义参数包扩展的"pattern",尤其是在函数调用中
- 是使用移动语义自动排序()的
- 移动语义在哪里是有益的好例子
- 如何使用c++获得文件夹/目录名称,但不是一个文件的路径?尤其是提高:文件系统;
- 内联在gcc中的行为:虚拟函数的内联,尤其是在我的例子中的析构函数
- 我是否正确地使用了move语义?好处是什么?