如何通过模板构造函数转发 C++ 基类和成员初始值设定项?

How to forward C++ base & member initializers through a template constructor?

我想制作一个模板,其参数 B 和 D 是模板实例化中固定数组的基础 class 和元素:

template <class B,class D>
struct fix_array : public B {
    D    myArray[5];
    /**/ fix_array( void ) { cout << "completely default constructed"; }
};

这很好用。接下来,我想将初始化值传递给 B:

template <class... BI>
/**/    fix_array( BI &&... bi )
           : B( std::forward<BI>(bi)... )
           , myArray( )
           {  cout << "This works too, even for multi-argument constructors of B."; }

这也行。最后,我的问题:

如何通过模板构造函数将初始化值传递给 D myArray[5];

我设想调用代码类似于:

fix_array<B,D>  myVal( { B-initializers }, { { D[0]-initializers }, { D[1]-initializers } } );

但我迷失在可变参数模板的地狱中。所有 D 初始值设定项都必须具有相同的形式吗? (假设 D 可以用 intconst char *D && 初始化。)

这是我的测试程序。 debug-util.hh定义了一个classVobject,在构造、析构、复制等时打印出信息

#include <stdio.h>
#include <memory.h>
#include <iostream>
#include <typeinfo>
#include <memory>
#include <vector>

#include "debug-util.hh"     // Defines Vobject and some other info-printing utilities

template <class B, class T>
class fix_array : public B
{
  public:
    T            pData[5];

    /**/         fix_array( void ) : B( ), pData( ) { std::cerr << "completely default construction" << std::endl; }

    template <class... BI>
    /**/         fix_array( BI &&... bi )
                   : B( std::forward<BI>(bi)... )
                   , pData( )
                   {
                       std::cerr << "fix_array Base was initialized" << std:: endl;
                   }
} ;

/*
 * A Vobject has 4 constructors: (void), (int), (int,char*), (int,int,int)
 *
 * It prints information about how it's constructed, when it's copied or moved,
 * and when it's destroyed.
 */

int
main( int, char ** )
{
    fix_array<Vobject,Vobject>   a;                 // These 4 initialize the Base Vobject
    fix_array<Vobject,Vobject>   b( 1 );            // using the various constructors.
    fix_array<Vobject,Vobject>   c( 3, "foobar" );
    fix_array<Vobject,Vobject>   d( 100, 200, 300 );

    // HOW CAN I PASS INITIALIZERS TO THE pDATA ARRAY MEMBERS???

    // fix_array<Vobject,Vobject> x( { 123, "base init" }, { { 1 }, { 2, "xyz" }, { 2, 4, 5 } } );

    std::cerr << "Hello, world" << std::endl;

    return 0;
}

(是的,我知道这是一个愚蠢的程序,使用 std::vector 等会更聪明。但我有一个更复杂的最终目标,学习如何使用可变参数模板会帮我拿到它。)

这个程序的执行:

Vobject@A000()
Vobject@A001()
Vobject@A002()
Vobject@A003()
Vobject@A004()
Vobject@A005()
completely default construction
Vobject@A006(1)
Vobject@A007()
Vobject@A008()
Vobject@A009()
Vobject@A010()
Vobject@A011()
fix_array Base was initialized
Vobject@A012(3, "foobar")
Vobject@A013()
Vobject@A014()
Vobject@A015()
Vobject@A016()
Vobject@A017()
fix_array Base was initialized
Vobject@A018(100,200,300)
Vobject@A019()
Vobject@A020()
Vobject@A021()
Vobject@A022()
Vobject@A023()
fix_array Base was initialized
Hello, world
~Vobject@A023 (was constructed Vobject(void))
~Vobject@A022 (was constructed Vobject(void))
~Vobject@A021 (was constructed Vobject(void))
~Vobject@A020 (was constructed Vobject(void))
~Vobject@A019 (was constructed Vobject(void))
~Vobject@A018 (was constructed Vobject(int,int,int))
~Vobject@A017 (was constructed Vobject(void))
~Vobject@A016 (was constructed Vobject(void))
~Vobject@A015 (was constructed Vobject(void))
~Vobject@A014 (was constructed Vobject(void))
~Vobject@A013 (was constructed Vobject(void))
~Vobject@A012 (was constructed Vobject(int,const char*))
~Vobject@A011 (was constructed Vobject(void))
~Vobject@A010 (was constructed Vobject(void))
~Vobject@A009 (was constructed Vobject(void))
~Vobject@A008 (was constructed Vobject(void))
~Vobject@A007 (was constructed Vobject(void))
~Vobject@A006 (was constructed Vobject(int))
~Vobject@A005 (was constructed Vobject(void))
~Vobject@A004 (was constructed Vobject(void))
~Vobject@A003 (was constructed Vobject(void))
~Vobject@A002 (was constructed Vobject(void))
~Vobject@A001 (was constructed Vobject(void))
~Vobject@A000 (was constructed Vobject(void))

我怀疑您在尝试同时通过两包时卡住了。您可能会被困在那里,因为这是最直观的事情。不幸的是,直觉在这里让我们失望,因为从裸函数参数中进行包推导的行为方式与直觉不符。尾随包的行为不同,很难知道第二包何时开始和第一包何时结束,并且一路上遇到的错误并不总是很清楚。

在这种情况下,我喜欢简单地做标准库做的事情。 std::pair 是聚合两个元素的类型。并且它允许通过将参数元组传递给每个元素(constructor 8). This synergizes with std::forward_as_tuple,一个标准函数模板来单独(就地)初始化每个元素。你可以为基数和数组提供参数做类似的事情.

剩下的问题只是如何提取元组元素。 B 在 C++17 的 std::make_from_tuple 中很容易。 myArray 没有相同的好处,因为 c-style 数组是不规则类型。然而,我们可以使用委托构造函数和实用程序 std::index_sequence 来构造数组。我也会将这种方法用于 B,因此总共只需要 C++14。

template <class B, class T>
class fix_array : public B
{
public:
    template <class... BRefs, class... DRefs>
    fix_array(std::tuple<BRefs...> bArgs, std::tuple<DRefs...> dArgs)
    : fix_array(std::move(bArgs), std::make_index_sequence<sizeof...(BRefs)>{}, std::move(dArgs), std::make_index_sequence<sizeof...(DRefs)>{})
    {}

private:
    T            pData[5];

    template <class... BRefs, std::size_t... Bidx, class... DRefs, std::size_t... Didx>
    fix_array(std::tuple<BRefs...> bArgs, std::index_sequence<Bidx...>, std::tuple<DRefs...> dArgs,  std::index_sequence<Didx...>)
    : B( std::get<Bidx>(bArgs)... )
    , pData{ std::get<Didx>(dArgs)... }
    {
        std::cerr << "fix_array Base was initialized" << std:: endl;
    }
};

两个index_sequence参数是标签类型占位符,按顺序包含每个元组元素的索引。它们只是在那里以正确的顺序扩展和访问每个元组元素。 std::getstd::forward_as_tuple 完成它的事情之后已经通过正确的引用类型提取了元素。

You can see it working in a live example.