继承构造函数 vs 转发

Inheriting constructors vs forwarding

C++11 允许继承构造函数,从而可以避免大量样板代码,尤其是使用包装器 class 之类的东西。但是,您似乎已经可以单独使用可变参数模板实现此功能。

class B
{
public:
 B(int){//do something}
 B(int, char){//do something}
};

使用继承构造函数:

class D : public B
{
public:
 using B::B; 
};

使用可变参数模板并转发:

class D : public B
{
public:
 template <typename...Args>
 D(Args&&... args) : B(std::forward<Args>(args)...)
 {
 }
};

虽然一致性(using 以相同的方式对待构造函数和方法)和易用性是将继承的构造函数引入语言的很好的理由,但还有其他原因可以首选第一个解决方案吗到第二个?我发现讨论继承构造函数的 CWG 文档 (N1890 and N1898) 只需注意以下内容并继续:

Little more than a historical accident prevents using this to work for a constructor as well as for an ordinary member function. Had a constructor been called “ctor” or “constructor” rather than being referred to by the name of their class, this would have worked. We propose this as the mechanism for inheriting constructors.

完美转发并不完美。特别是,作为实际参数的 0 减少为 int 而不是表示一般的空值。因此,对于采用指针参数的构造函数,0 将作为继承构造函数的实际参数,但不适用于转发。

我想我曾在播出问题(我们真的需要继承构造函数)时提到的另一个原因是,继承构造函数是概念上简单事物的简单表示法,用于一个简单的class。 C++ 中有足够的强制复杂性和强制样板文件。

很大的原因是完美转发并不完美。简单案例:

struct B {
    B(int* ) { .. }
};

现在考虑:

D d{0};

如果我们继承构造函数,就可以正常工作。如果我们完美转发,我们将尝试构造 B(int ),因为 0 推导为 int,这是一个不存在的构造函数。失败!

其他失败案例包括大括号初始化、仅声明静态常量数据成员、重载函数和位域。

维护问题(编译器错误)可能导致偏好 'using':

struct Base
{
    Base(int) {}
};

struct Derived : public Base
{
    using Base::Base;
};

struct DerivedTemplate : Base
{
    template <typename...Args>
    DerivedTemplate(Args&&... args)
    : Base(std::forward<Args>(args)...)
    {}
};

int main()
{
    // error: no matching function for call to ‘Derived::Derived(int, int)’
    Derived d(1, 2);

    // In instantiation of ‘DerivedTemplate::DerivedTemplate(Args&& ...) [with Args = {int, int}]’:
    // required from here
    // error: no matching function for call to ‘Base::Base(int, int)’
    DerivedTemplate dt(1, 2);
}

此外,每个模板实例化都是一个不同的构造函数(虽然使用不相乘)