c++实现可变参数模板函数_C ++中的可变参数模板:实现简单的元组

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++实现可变参数模板函数

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值