具有std :: initializer_list的非递归可变参数模板

For some time now I've been spending a lot of time learning new features from C++14/C++17, and since in my opinion there are far too few articles about it here, I've decided to share a few newly learned things here.

一段时间以来,我一直在花很多时间来学习C ++ 14 / C ++ 17的新功能,由于我认为此处的文章太少,因此我决定分享一些在这里新学到的东西。

This article is about variadic templates, which in my opinion is one of the greatest new features because it makes it very easy to write classes and functions that can be used very flexibly.

本文是关于可变参数模板的,在我看来,可变参数模板是最大的新功能之一,因为它使编写可以灵活使用的类和函数变得非常容易。

Note 1: I didn't use any #include in the code below, I guess everyone who understands this article knows, which headers are needed.

注意1 :在下面的代码中我没有使用任何#include,我想所有了解本文的人都知道需要哪些标头。

Note 2: In the code below I want to set the focus on variadic templates, so I do not use things like explicit move-semantic, forwarding or universal references here because it would make the code samples harder to read. IMO move-semantics, forwarding, and universal references are another subject, and I hope someone else is writing an article about it.

注意2 :在下面的代码中,我希望重点关注可变参数模板,因此在这里我不使用显式移动语义,转发或通用引用之类的东西,因为这会使代码示例更难阅读。 IMO的移动语义,转发和通用引用是另一个主题,我希望其他人正在撰写有关此主题的文章。

Disclaimer: The code may contain errors, and I'm sure there are things which could be done better. Please don't hesitate to tell me when you think you found a bug or a better way to do something.

免责声明 :代码中可能包含错误,我敢肯定有些事情可以做得更好。 当您认为自己发现了错误或做某事的更好方法时,请随时告诉我。

First I show a very generic case of a variadic template to demonstrate how it is intended to be used.

首先,我展示了可变参数模板的一个非常普通的情况,以演示如何使用它。

Consider this example:

考虑以下示例:

// function template to print out one single value
template < typename T >
void print( T&& val )
{
    std::cout << val;
}

// function template to print out the first passed argument
// and to 'call' itself recursively with remaining elements
template < typename T, typename ... ARGS >
void print( T&& val, ARGS&& ... args )
{
    print( val );
    print( std::forward< ARGS >( args ) ... );
}

int main()
{
    print( "Hello," , std::string{ " world: " }, 42, 3.1415, '\n' );
} 

This is a pretty easy example to demonstrate how powerful variadic templates are.

这是一个非常简单的示例,展示了可变参数模板的功能。

But there's one drawback with this: This kind of implementing variadic templates generates compile-time recursion, each time the variadic print is called it picks out the first element and re-calls itself with the remaining arguments thus creating a new function.

但是这样做有一个缺点:这种实现可变参数的模板会生成编译时递归,每次调用可变参数打印时 ,它都会选择第一个元素,并使用其余参数重新调用自身,从而创建一个新函数。

In the given example this means the call to print in main will make the compiler creating these functions:

在给定的示例中,这意味着对main的 打印将使编译器创建以下功能:

// these are of course needed, they're created from the none-variadic template for each used type
void print( char ) { ... }
void print( double ) { ... }
void print( int ) { ... }
void print( std::string ) { ... }
void print( const char* ) { ... }

// this is even needed, it's created from the function call
void print( const char*, std::string, int, double, char ) { ... }

// these are created due to compile-time recursion
void print( double, char ) { ... }
void print( int, double, char ) { ... }
void print( std::string, int, double, char ) { ... } 

Now there's a kind of a trick to get this implemented without need of compile-time recursion. For this we use a std::initializer_list like this (description will follow):

现在,有一种技巧可以在无需编译时递归的情况下实现此目标。 为此,我们使用这样的std :: initializer_list (后面将进行描述):

template < typename ... ARGS >
void println( ARGS&& ... args )
{
    // the (void) is just used to avoid a 'expression result unused' warning
    (void)std::initializer_list< int >{ ( std::cout << args, 0 ) ... };
    std::cout << '\n';
} 

The trick is to put the functionality which should be used for each single argument as statements to the constructor of a temporarily created instance of a std::initializer_list< int > - the compiler's pack expansion (this is the functionality used to replace the ... by arguments used in a function call) creates a comma-separated list of everything which is inside of the parenthesis.

技巧是将应用于每个单个参数的功能作为对std :: initializer_list <int>的临时创建实例的构造函数的语句的声明-编译器的pack扩展(这是用于替换..的功能。 (由函数调用中使用的参数组成),会创建一个用逗号分隔的列表,其中包含括号内的所有内容。

So for each parameter passed to the function one int is added to the comman-seperated initialization list which is passed to the constructor of std::initializer_list in a form of { ( arg1, 0 ), ( arg2, 0 ), ( arg3, 0 ) /*,a.s.o*/ ).

因此,对于传递给函数的每个参数,将一个int添加到以逗号分隔的初始化列表中,该初始化列表以{(arg1,0),(arg2,0),(arg3,形式传递给std :: initializer_list的构造函数。 0)/ *,aso * /)

Each of the inner statements evaluates to 0 (this is the value passed to the initialization list-constructor), but an arbitrary number of statements can be evaluated before the comma in front of the 0 - this is the place where the functionality (std::cout << argx) is executed.

每个内部语句的计算结果均为0 (这是传递给初始化列表构造函数的值),但是可以在0前面的逗号之前对任意数量的语句进行求 -这是功能( std: :cout << ar g x )被执行。

Fortunately, the way this is implemented ensures everything is executed in the same order because a comma-separated list of statements is guaranteed to be evaluated from left to right (since comma-operators are sequence points).

幸运的是,这种实现方式可确保所有内容均以相同的顺序执行,因为可以保证从左到右评估逗号分隔的语句列表(因为逗号是顺序点)。

So it's even possible to safely use multiple functions with each argument, thus allowing us to do quite complicated things like i.e. a function, which creates a vector of strings from an arbitrary number of arguments of arbitrary types:

因此,甚至可以对每个参数安全地使用多个函数,从而使我们能够执行非常复杂的事情,例如,一个函数,该函数从任意数量的任意类型的参数创建字符串向量:

template < typename T >
std::string make_string( const T& val )
{
    std::stringstream ss;
    ss << val;
    return ss.str();
}

template < typename ... ARGS >
std::vector< std::string > make_string_list( ARGS&& ... args )
{
    std::vector< std::string > v;
    std::string s;
    std::initializer_list< int >{
        (
            s = make_string( args ),
            std::clog << "Adding: '" << args << "' [" << typeid( args ).name() << "]",
            v.push_back( s ),
            std::clog << " done - verify: '" << v.back() << "'\n",
            0) ...
    };
    return v;
}

int main()
{
    auto l = make_string_list( "Hello", ',', std::string{ " world: " }, 42, 3.1415 );
} 

I know, the syntax is horrible, but hey, this is really, really useful (and efficient too) - here's the logged output:

我知道,语法太可怕了,但是,嘿,这确实非常有用(而且非常有效)-这是记录的输出:

Adding: 'Hello' [char const [6]] done - verify: Hello
Adding: ',' [char] done - verify: ,
Adding: ' world: ' [class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >] done - verify:  world:
Adding: '42' [int] done - verify: 42
Adding: '3.1415' [double] done - verify: 3.1415 

IMO this is quite cool.

IMO这很酷。

That's the theoretical part, as some kind of reward I want to share an implementation I made which I use myself often since it makes my life much easier.

那是理论上的部分,作为一种奖励,我想分享我经常使用的一种实现方式,因为它使我的生活更加轻松。



Note: The following code will only be of special interest for those who work on GUI implementing with VisualStudio and MFC. If you don't use these you can jump to the end - sorry.

注意:以下代码仅对那些使用 VisualStudio和MFC 实现GUI的人员特别感兴趣 如果您不使用这些,则可以跳到最后-抱歉。



There was one thing I found annoying and boring whenever I had to do: filling report-style CListCtrls and updating items.

每当我必须做的时候,我都会感到烦恼和无聊:填充报表样式的CListCtrls和更新项目。

With variadic templates, I managed to make this much easier.

使用可变参数模板,我设法使这一过程变得更加容易。

I'll first show samples about how I do things now, the code I need for it follows below.

我将首先显示有关我现在的工作方式的示例,下面是我所需的代码。

Consider having a Dialog class with a CListCtrl-member mapped to a list control, in OnInitDialog this list should be filled with data elements like this:

考虑将Dialog类的CListCtrl -member映射到列表控件,在OnInitDialog中,此列表应填充如下数据元素:

struct data
{
    int id = 0;
    std::string name;
    double val = 1.0;
    bool state = true;
};

std::vector< data > m_data; 

My common way to do this before was somehow like this:

我以前这样做的常见方式是这样的:

class CTestDlg : public CDialog
{
    // ...
    CListCtrl m_list;
};

BOOL CTestDlg::OnInitDialog()
{
    // ...
    m_list.InsertColumn( 0, "id" );
    m_list.InsertColumn( 1, "name" );
    m_list.InsertColumn( 2, "val" );
    m_list.InsertColumn( 3, "state" );

    for ( auto data : m_data )
    {
        double v1 = GetTestVal();
        double v2 = GetTestVal();

        int index = m_list.InsertItem( m_list.GetItemCount(), std::to_string( m_data.id ).c_str() );
        m_list.SetItemText( n, 1, data.name.c_str() );
        m_list.SetItemText( n, 2, std::to_string( m_data.val ).c_str() );
        m_list.SetItemText( n, 3, std::to_string( m_data.state ).c_str() );
    }
} 

With my extension I now can do it this way:

通过扩展,我现在可以这样:

class CTestDlg : public CDialog
{
    // ...
    CExtListCtrl m_list; // my improved class
};

BOOL CTestDlg::OnInitDialog()
{
    m_list.CreateColumns( "id", "name", "val", "state" );

    for ( auto data : m_data )
    {
        m_list.AddRow( m_data.id, m_data.name, m_data.val, m_data.state );
    }
} 

Cool, isn't it?

不错,不是吗?

Further with my extension, I can set existing items, even partial, i.e.:

通过扩展,我可以设置现有项目,甚至是部分项目,即:

void CTestDlg::ResetValues()
{
    for ( int n = 0; n < m_list.GetItemCount(); n++ )
    {
        m_list.SetRowItems( n, nullptr, nullptr, 1.0, true );
    }
} 

This replaces all items in the third and the fourth row while the first two rows are untouched.

这将替换第三行和第四行中的所有项目,而前两行保持不变。

And, as last I decided to use this all to implement a kind of type-safe CListCtrl which can be used like this:

并且,最后我决定全部使用它来实现一种类型安全的CListCtrl ,可以像这样使用:

class CTestDlg : public CDialog
{
    // ...
    CTypedListCtrl < int, std::string, double, bool > m_dynList;
    std::vector< data > m_data;
};

BOOL CTestDlg::OnInitDialog()
{
    m_list.CreateColumns( "id", "name", "val", "state" );
   
    m_list.AddRow( 1, "Test", -1.0, false ); // ok
    m_list.AddRow( "2", "Test", -1.0, false ); // compile error: cannot convert argument 1 from 'const char [1]' to 'int'
} 


I really like this, since I have this CListCtrls stopped bothering me - here's the header-only code of my implementation:

我真的很喜欢,因为我已经让CListCtrls不再困扰我了-这是实现的仅标头代码:

// First here are some general helper functions to make using CListCtrl more convenient

namespace CListCtrlHelper
{
    inline void Reset( CListCtrl& ctrl )
    {
        ctrl.DeleteAllItems();

        if ( CHeaderCtrl* pHeader = ctrl.GetHeaderCtrl() )
        {
            while ( pHeader->GetItemCount() > 0 )
            {
                ctrl.DeleteColumn( 0 );
            }
        }
    }

    inline unsigned int GetColumnCount( CListCtrl& ctrl )
    {
        return nullptr == ctrl.GetHeaderCtrl() ? 0 : ctrl.GetHeaderCtrl()->GetItemCount();
    }

    // Info: the parameter 'bSet' is for convenience: it is useful in case this is called
    // from a function which itself has a flag whether to use SetRedraw-mechanism at all,
    // instead of the need to check such a flag in the callee the flag can simply passed.
    inline void SetListCtrlRedraw( CListCtrl& ctrl, const bool bRedraw = true, const bool bSet = true )
    {
        if ( false == bSet )
        {
            return;
        }

        ctrl.SetRedraw( false != bRedraw );

        if ( nullptr != ctrl.GetHeaderCtrl() )
        {
            ctrl.GetHeaderCtrl()->SetRedraw( false != bRedraw );
        }

        if ( false != bRedraw )
        {
            ctrl.Invalidate();
            ctrl.UpdateWindow();
        }
    }

    inline void    AutoAdjustColumnWidths( CListCtrl& ctrl, bool bSetRedraw = true )
    {
        CHeaderCtrl* pHeader = ctrl.GetHeaderCtrl();

        if ( nullptr != pHeader )
        {
            SetListCtrlRedraw( ctrl, bSetRedraw, false );

            for ( int i = 0; i < pHeader->GetItemCount(); i++ )
            {
                // find max of content vs header
                ctrl.SetColumnWidth( i, LVSCW_AUTOSIZE );
                int nColumnWidth = ctrl.GetColumnWidth( i );

                ctrl.SetColumnWidth( i, LVSCW_AUTOSIZE_USEHEADER );
                int nHeaderWidth = ctrl.GetColumnWidth( i );

                // set width to max
                ctrl.SetColumnWidth( i, max( nColumnWidth, nHeaderWidth ) );
            }

            pHeader->RedrawWindow( nullptr, nullptr, RDW_ALLCHILDREN | RDW_ERASE | RDW_FRAME | RDW_UPDATENOW );

            SetListCtrlRedraw( ctrl, bSetRedraw, true );
        }
    }
} // CListCtrlHelper 

Here the basic variadic templates are implemented:

这里实现了基本的可变参数模板:

namespace CExtListCtrlHelper
{
    using namespace CListCtrlHelper;

    template < typename T >
    std::string ToString( T param, const std::locale loc = std::locale::classic() )
    {
        std::stringstream ss;
        ss.imbue( loc );
        ss << param;
        return std::move( ss.str() );
    }

    inline void AppendColumn( const unsigned int col, CListCtrl& ctrl, const char* param )
    {
        ctrl.InsertColumn( col, param );
    }

    inline void AppendColumn( const unsigned int col, CListCtrl& ctrl, const std::string& param )
    {
        AppendColumn( col, ctrl, param.c_str() );
    }

    template < typename T >
    inline void AppendColumn( const unsigned int col, CListCtrl& ctrl, T param )
    {
        AppendColumn( col, ctrl, std::move( ToString( param ) ) );
    }

    template < typename ... ARGS >
    inline void AppendColumns( const unsigned int col, CListCtrl& ctrl, ARGS ... args )
    {
        int count = -1;
        (void)std::initializer_list< int >{ ( AppendColumn( ( count = count + 1, col + count ), ctrl, args ), 0 ) ... };
    }

    template < typename ... ARGS >
    inline void    CreateColumns( CListCtrl& ctrl, ARGS ... args )
    {
        Reset( ctrl );
        AppendColumns( 0, ctrl, args ... );
    }

    inline void SetRowItem( const unsigned int row, const unsigned int col, CListCtrl& ctrl, std::nullptr_t )
    {
        /*do nothing: nullptr is used as placeholder*/
    }

    inline void SetRowItem( const unsigned int row, const unsigned int col, CListCtrl& ctrl, char* param )
    {
        ctrl.SetItemText( row, col, param );
    }

    inline void SetRowItem( const unsigned int row, const unsigned int col, CListCtrl& ctrl, std::string param )
    {
        ctrl.SetItemText( row, col, param.c_str() );
    }

    template < typename T >
    inline void SetRowItem( const unsigned int row, const unsigned int col, CListCtrl& ctrl, T param )
    {
        if ( col >= GetColumnCount( ctrl ) )
        {
            return;
        }

        SetRowItem( row, col, ctrl, std::move( ToString( param ) ) );
    }

    template < typename ... ARGS >
    inline void SetRowItems( const unsigned int row, const unsigned int col, CListCtrl& ctrl, ARGS ... args )
    {
        int count = -1;
        (void)std::initializer_list< int >{ ( SetRowItem( row, ( count = count + 1, col + count ), ctrl, args ), 0 ) ... };
    }

    template < typename ... ARGS >
    inline void SetRowItems( const unsigned int row, CListCtrl& ctrl, ARGS ... args )
    {
        SetRowItems( row, 0u, ctrl, args ... );
    }

    template < typename ... ARGS >
    inline const unsigned int InsertRow( const unsigned int row, CListCtrl& ctrl, ARGS ... args )
    {
        unsigned int index = ctrl.InsertItem( row, "", 0 );
        SetRowItems( index, ctrl, args ... );
        return index;
    }

    template < typename ... ARGS >
    inline const unsigned int AddRow( CListCtrl& ctrl, ARGS ... args )
    {
        return InsertRow( ctrl.GetItemCount(), ctrl, args ... );
    }
}; 

Next a CListCtrl-derived class which wraps these helpers:

接下来是CListCtrl派生的类,其中包装了这些帮助器:

class CExtListCtrl : public CListCtrl
{
public:
    void    Reset()
    {
        CExtListCtrlHelper::Reset( *this );
    }

    void    AutoAdjustColumnWidths( bool bSetRedraw = true )
    {
        CExtListCtrlHelper::AutoAdjustColumnWidths( *this, bSetRedraw );
    }

    template < typename T >
    void SetRowItem( const unsigned int row, const unsigned int col, T param )
    {
        CExtListCtrlHelper::SetItemText( *this, row, col, std::move( std::to_string( param ).c_str() ) );
    }

    template < typename T, typename ... ARGS >
    void SetRowItems( const unsigned int row, const unsigned int col, T param, ARGS ... args )
    {
        CExtListCtrlHelper::SetRowItem( *this, col, param, args ... );
    }

    template < typename T >
    void AppendColumn( const unsigned int col, T param )
    {
        CExtListCtrlHelper::AppendColumn( *this, col, std::move( std::to_string( param ) ) );
    }

    template < typename ... ARGS >
    void AppendColumns( const unsigned int col, std::string title, ARGS ... args )
    {
        CExtListCtrlHelper::AppendColumns( col, *this, title, args ... );
    }

    template < typename ... ARGS >
    void    CreateColumns( ARGS ... args )
    {
        CExtListCtrlHelper::CreateColumns( *this, args ... );
    }
}; 

And finally here's the CListCtrl-derived class CTypedListCtrl:

最后是CListCtrl派生的类CTypedListCtrl:

template < typename ... ARGS >
class CTypedListCtrl : public CExtListCtrl
{
public:
    unsigned int AddRow( ARGS ... args )
    {
        return CExtListCtrlHelper::AddRow( *this, args ... );
    }
}; 


That's all,

就这样,

thanks for reading, I hope it was at least a bit informative for you,

感谢您的阅读,希望至少对您有所帮助,

best regards,

最好的祝福,

ZOPPO

佐普

PS: I learned most of this reading and watching tutorials, and I want to especially mention (and thank) Jason Turner who released a lot of very interesting video tutorials on YouTube, i.e. I found the trick with std::initializer_list in a video of his great C++ Weekly episodes at https://www.youtube.com/watch?v=VXi0AOQ0PF0&index=4&list=PLs3KjaCtOwSZ2tbuV1hx8Xz-rFZTan2J1

PS:我学习了大部分阅读和观看教程,并且我想特别提及(并感谢)Jason Turner,他在YouTube上发布了很多非常有趣的视频教程,即我在以下视频中找到了std :: initializer_list的窍门在https://www.youtube.com/watch?v=VXi0AOQ0PF0&index=4&list=PLs3KjaCtOwSZ2tbuV1hx8Xz-rFZTan2J1他出色的C ++每周剧集

翻译自: https://www.experts-exchange.com/articles/32502/None-recursive-variadic-templates-with-std-initializer-list.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值