c++实现可变参数模板函数
从C ++ 11开始, std :: tuple是对Modern C ++的不可思议的扩展,它提供了固定大小的异构值集合。 不幸的是,元组在以传统方式进行管理时可能有些怀疑。 但是,随后发布的C ++标准引入了一些功能和帮助程序,这些功能和帮助程序大大减少了必要的样板。 因此,在本文中,我将借助简单的元组来解释C ++中的可变参数模板
实施。 并且还会引导您完成元组的棘手部分,即
通过元组元素循环。 尽管我在上一篇文章中笼罩了可变参数模板,即C ++ Template:A Quick UpToDate Look 。 因此,我的重点是将可变参数模板和元组实现与更多最新的C ++规范结合在一起。
动机
- 定义接受可变数量和参数类型的类/结构/联合/函数通常很有用。
- 如果您已经使用过C,您将知道printf函数可以接受任意数量的参数。 这些功能完全通过宏或省略号运算符实现 。 因此,它具有一些缺点,例如类型安全 ,不能接受引用作为参数,等等。
可变参数类模板:实现元组类
- 因此,让我们借助可变参数模板构建与std :: tuple相同的ADT 。
- C ++中的可变参数模板通常以通用(空)定义开头,在以后的专业中,它也用作模板递归终止的基础。
template < typename ... T>
struct Tuple { };
- 这已经允许我们定义一个空结构,即Tuple <>对象;尽管它不是很有用。 接下来是递归案例专业化:
template <
typename T,
typename ... Rest // Template parameter pack
>
struct Tuple <T, Rest...> { // Class parameter pack
T first;
Tuple<Rest...> rest; // Parameter pack expansion
Tuple( const T& f, const Rest& ... r)
: first(f)
, rest(r...) {
}
};
Tuple< bool > t1( false ); // Case 1
Tuple< int , char , string > t2( 1 , 'a' , "ABC" ); // Case 2
可变参数类模板如何工作?
要了解可变参数类模板,请考虑上面的用例2,即
Tuple<int , char , string > t2( 1 , 'a' , "ABC" );
- 声明首先与专业化匹配,产生一个带有int的结构; 和Tuple <char,string> rest; 数据成员。
- 其余的定义再次与专业化匹配,首先产生一个具有char的结构; 和元组<string>休息; 数据成员。
- 其余的定义再次与该专业化匹配,首先创建其自己的字符串。 和元组<>休息; 成员。
- 最后,最后的其余部分与基本情况定义匹配,从而产生一个空结构。
您可以将其形象化如下:
Tuple < int , char , string >
-> int first
-> Tuple < char , string > rest
-> char first
-> Tuple < string > rest
-> string first
-> Tuple <> rest
-> (empty)
可变参数函数模板:为元组类实现get <>()函数
- 到目前为止,我们已经设计了具有可变数量和类型的数据成员的数据结构。 但是,它仍然没有用,因为没有机制可以从Tuple类中检索数据。 因此,让我们设计一个:
template <
size_t idx,
template < typename ...> class Tuple ,
typename ... Args
>
auto get ( Tuple <Args...> & t ) {
return GetHelper<idx, Tuple<Args...>>::get(t);
}
- 如您所见,此get函数在idx上进行了模板化。 因此用法可以类似于get <1>(t),类似于std :: tuple 。 但是,实际的工作是通过帮助程序类(即GetHelper)中的静态函数完成的。
- 还请注意,使用C ++ 14样式的自动返回类型会使我们的生活变得更加简单,否则,返回类型将需要相当复杂的表达式。
- 因此,进入助手类。 这次,我们将需要一个空的前向声明和两个专门化的东西。 首先是空声明:
template <
size_t idx,
typename T
>
struct GetHelper ;
- 现在是基本情况(当idx == 0时)。 在这个专业中,我们只返回第一个成员:
template <
typename T,
typename ... Rest
>
struct GetHelper <0, Tuple<T, Rest...>> {
static T get (Tuple<T, Rest...> &data) {
return data.first;
}
};
- 在递归的情况下,我们递减idx并为其余成员调用GetHelper:
template <
size_t idx,
typename T,
typename ... Rest
>
struct GetHelper <idx, Tuple<T, Rest...>> {
static auto get (Tuple<T, Rest...> &data) {
return GetHelper<idx - 1 , Tuple<Rest...>>::get(data.rest);
}
};
- 为了完成一个示例,假设我们有元组数据,并且需要get <1>(data)。
- 这将调用GetHelper <1,Tuple <T,Rest ... >>> :: get(data)(第二个专业化)。
- 依次调用GetHelper <0,Tuple <T,Rest ... >>> :: get(data.rest)。
- 最后返回(通过第一个专业化,因为现在idx为0)data.rest.first。
就是这样了! 这是整个功能代码,并在主要功能中使用了一些示例:
// Forward Declaration & Base Case -----------------------------------------
template <
size_t idx,
typename T
>
struct GetHelper { };
template < typename ... T>
struct Tuple { };
// -------------------------------------------------------------------------
// GetHelper ---------------------------------------------------------------
template <
typename T,
typename ... Rest
>
struct GetHelper <0, Tuple<T, Rest...>> { // Specialization for index 0
static T get (Tuple<T, Rest...> &data) {
return data.first;
}
};
template <
size_t idx,
typename T,
typename ... Rest
>
struct GetHelper <idx, Tuple<T, Rest...>> { // GetHelper Implementation
static auto get (Tuple<T, Rest...> &data) {
return GetHelper<idx - 1 , Tuple<Rest...>>::get(data.rest);
}
};
// -------------------------------------------------------------------------
// Tuple Implementation ----------------------------------------------------
template <
typename T,
typename ... Rest
>
struct Tuple <T, Rest...> {
T first;
Tuple<Rest...> rest;
Tuple( const T &f, const Rest &... r)
: first(f)
, rest(r...) {
}
};
// -------------------------------------------------------------------------
// get Implementation ------------------------------------------------------
template <
size_t idx,
template < typename ...> class Tuple ,
typename ... Args
>
auto get ( Tuple <Args...> & t ) {
return GetHelper<idx, Tuple<Args...>>::get(t);
}
// -------------------------------------------------------------------------
int main () {
Tuple< int , char , string > t( 500 , 'a' , "ABC" );
cout << get< 1 >(t) << endl ;
return 0 ;
}
可变参数模板与折叠表达
- 有两种方式处理C ++参数包,即
1.递归
2.折叠表达式(来自C ++ 17) - 在任何可能的地方,我们都应该使用fold表达式处理参数包,而不要使用递归。 因为它有一些好处
如:
1.减少编写代码
2.更快的代码(无优化),因为您只有一个表达式而不是多个函数调用
3.处理更少的模板实例化,编译速度更快
递归处理参数包
- 如前所述,可变参数模板以空定义开始,即递归的基本情况。
void print () {}
- 然后,递归案例专业化:
template <
typename First,
typename ... Rest // Template parameter pack
>
void print (First first, Rest... rest) { // Function parameter pack
cout << first << endl ;
print(rest...); // Parameter pack expansion
}
- 现在,这足以让我们使用带有可变数量和参数类型的print函数。 例如:
print(500 , 'a' , "ABC" );
使用折叠表达式处理参数包
template < typename ... Args>
void print (Args... args) {
( void ( cout << args << endl ), ...);
}
- 参见,不需要神秘的样板。 这个解决方案看起来不是很整洁吗?
- 共有3种折叠类型:一元折叠,二元折叠和逗号折叠。 在这里,我们已经完成了逗号折叠。 您可以在此处阅读有关折叠表达的更多信息。
在C ++中循环/遍历元组元素
- 如果我给您一个打印元组元素的任务,您想到的第一件事是:
template < typename ... Args>
void print ( const std ::tuple<Args...> &t) {
for ( const auto &elem : t) // Error: no begin/end iterator
cout << elem << endl ;
}
- 但是,这根本行不通。 std :: tuple没有开始和结束迭代器。
- 好! 因此,现在您可以尝试原始循环了吗?
template < typename ... Args>
void print ( const std ::tuple<Args...>& t) {
for ( int i = 0 ; i < sizeof ...(Args); ++i)
cout << std ::get<i>(t) << endl ; // Error :( , `i` needs to be compile time constant
}
- 没有! 你不能。 我知道std :: get <>可以将数字用作非类型模板参数 。
- 但是,该数字必须是编译时常数才能使其正常运行。 因此,有很多解决方案,我们将介绍足够多的解决方案。
C ++ 11:遍历元组元素
// Template recursion
template < size_t i, typename ... Args>
struct printer {
static void print ( const tuple<Args...> &t) {
cout << get<i>(t) << endl ;
printer<i + 1 , Args...>::print(t);
}
};
// Terminating template specialisation
template < typename ... Args>
struct printer <sizeof...(Args), Args...> {
static void print ( const tuple<Args...> &) {}
};
template < typename ... Args>
void print ( const tuple<Args...> &t) {
printer< 0 , Args...>::print(t);
}
tuple< int , char , string > t( 1 , 'A' , "ABC" );
print(t);
// Note: might not work in GCC, I've used clang
- 相信我,这看起来并不复杂。 如果您知道递归和模板的专业知识,那么您花费不超过30秒即可了解此处的情况。
- 对于我们的示例tuple <int,char,string> t(1,'A',“ ABC”);, printer :: print()调用模板递归i,e,template <size_t i,typename…Args>结构打印机{ }; 每次使用递增的非类型模板参数i。 当i == sizeof…(Args)时,我们的调用通过调用模板专门化来停止,即template <typename…Args> struct printer <sizeof…(Args),Args…> {};。
C ++ 17:遍历元组元素
- 使用C ++ 17,由于我们有Fold Expressions,所以它会稍微好一些。 因此,我们不再需要递归。
template < typename ... Args>
void print ( const std ::tuple<Args...> &t) {
std ::apply([]( const auto &... args) {
(( cout << args << endl ), ...);
}, t);
}
- std :: apply设计为接受functor或lambda表达式的元组助手。 尽管如果希望根据类型分派给不同的实现,可以做得更好,但是您可以使用重载的类:
template < class ... Ts >
struct overloaded : Ts... {
using Ts:: operator ()...;
};
// Deduction guide, google `CTAD for aggregates` for more info
template < class ... Ts >
overloaded ( Ts ...) -> overloaded <Ts...>; // not needed from C++20
auto f = overloaded {
[]( const int &a) { cout << "From int: " << a << endl ; },
[]( const char &b) { cout << "From char: " << b << endl ; },
[]( const string &c) { cout << "From string: " << c << endl ; },
};
tuple< int , char , string > t( 1 , 'A' , "ABC" );
std ::apply([&]( const auto &... e) { (f(e), ...); }, t);
C ++ 23:遍历元组元素
template < typename ... Args>
void print ( const std ::tuple<Args...> &t) {
for ... ( const auto &elem : t)
cout << elem << endl ;
}
- 因此,从C ++ 23开始,我们可能会有扩展声明,即for ...()。 看起来像一个循环,尽管事实并非如此。 它只是将每个调用的作用域定义为:
template < typename ... Args>
void print ( const tuple<Args...> &t) {
{
const auto &elem = get< 0 >(t);
cout << elem << endl ;
}
{
const auto &elem = get< 1 >(t);
cout << elem << endl ;
}
{
const auto &elem = get< 2 >(t);
cout << elem << endl ;
}
}
- 很明显,没有中断并继续,因为它不是循环。
- 它基本上适用于每个可以通过std :: get <>()访问的标准容器。 例如,普通数组,std :: tuple,std :: pair,std :: array,未扩展的参数包,constexpr范围等。
结束语
在我们的元组类中仍然缺少很多东西,例如复制构造函数 , 移动构造函数 ,一些运算符和帮助程序类(例如std :: tuple_size )。 但是,我希望您现在知道如何使用
可变参数模板。 顺便说一句,实现那些遗漏的东西将是您自己学习可变参数模板的良好起点。
先前发布在 http://www.vishalchovatiya.com/variadic-template-cpp-implementing-unsophisticated-tuple/
翻译自: https://hackernoon.com/variadic-template-in-c-implementing-unsophisticated-tuple-w8153ump
c++实现可变参数模板函数