除当前类型外,可用于从Base派生的所有类型的隐式构造函数

Implicit constructor available for all types derived from Base excepted the current type?

本文关键字:类型 派生 构造函数 Base 用于      更新时间:2023-10-16

以下代码总结了我的问题:

template<class Parameter>
class Base {};
template<class Parameter1, class Parameter2, class Parameter>
class Derived1 : public Base<Parameter>
{ };
template<class Parameter1, class Parameter2, class Parameter>
class Derived2 : public Base<Parameter>
{
public :
    // Copy constructor
    Derived2(const Derived2& x);
    // An EXPLICIT constructor that does a special conversion for a Derived2
    // with other template parameters
    template<class OtherParameter1, class OtherParameter2, class OtherParameter>
    explicit Derived2(
        const Derived2<OtherParameter1, OtherParameter2, OtherParameter>& x
    );
    // Now the problem : I want an IMPLICIT constructor that will work for every
    // type derived from Base EXCEPT
    // Derived2<OtherParameter1, OtherParameter2, OtherParameter> 
    template<class Type, class = typename std::enable_if</* SOMETHING */>::type>
    Derived2(const Type& x);
};

考虑到我已经有了示例代码中的显式构造函数,如何将隐式构造函数限制为从父类派生的所有类(当前类除外,无论其模板参数如何)?

编辑:对于Base中的隐式构造函数,我显然可以写:

template<class OtherParameter> Derived2(const Base<OtherParameter>& x);

但在这种情况下,我是否可以保证编译器不会将此构造函数用作Derived2<OtherParameter1, OtherParameter2, OtherParameter>的隐式构造函数?

第2版:在这里我有一个测试:(LWS在这里:http://liveworkspace.org/code/cd423fb44fb4c97bc3b843732d837abc)

#include <iostream>
template<typename Type> class Base {};
template<typename Type> class Other : public Base<Type> {};
template<typename Type> class Derived : public Base<Type>
{
    public:
        Derived() {std::cout<<"empty"<<std::endl;}
        Derived(const Derived<Type>& x) {std::cout<<"copy"<<std::endl;}
        template<typename OtherType> explicit Derived(const Derived<OtherType>& x) {std::cout<<"explicit"<<std::endl;}
        template<typename OtherType> Derived(const Base<OtherType>& x) {std::cout<<"implicit"<<std::endl;}
};
int main()
{
    Other<int> other0;
    Other<double> other1;
    std::cout<<"1 = ";
    Derived<int> dint1;                     // <- empty
    std::cout<<"2 = ";
    Derived<int> dint2;                     // <- empty
    std::cout<<"3 = ";
    Derived<double> ddouble;                // <- empty
    std::cout<<"4 = ";
    Derived<double> ddouble1(ddouble);      // <- copy
    std::cout<<"5 = ";
    Derived<double> ddouble2(dint1);        // <- explicit
    std::cout<<"6 = ";
    ddouble = other0;                       // <- implicit
    std::cout<<"7 = ";
    ddouble = other1;                       // <- implicit
    std::cout<<"8 = ";
    ddouble = ddouble2;                     // <- nothing (normal : default assignment)
    std::cout<<"n9 = ";
    ddouble = Derived<double>(dint1);       // <- explicit
    std::cout<<"10 = ";
    ddouble = dint2;                        // <- implicit : WHY ?!?!
    return 0;
}

最后一行让我担心。它符合C++标准吗?这是g++的bug吗?

由于您引用的每个构造函数都是模板化的类方法,因此会调用模板实例化和函数重载解析的规则。

如果你看一下C++11标准的14.8.3节,实际上在第1-3段中有一些例子在一定程度上证明了你的问题。基本上,C++编译器将在一系列重载的模板函数中寻找最佳匹配或"最不通用"的模板函数实例化(必要时添加类型转换)。在您的情况下,因为您已显式创建了一个构造函数,该构造函数采用Derived2对象的备用实例化,因此与采用泛型类型T甚至Base<OtherParameter>参数的构造函数相比,该构造函数将是任何Derived2<...>类型的首选重载。

更新:显然,根据C++11标准中的12.3.1/2,

显式构造函数像非显式构造函数一样构造对象,但只在直接初始化语法(8.5)或显式使用强制转换(5.2.9、5.4)。

这意味着,如果不使用直接初始化语法来构造对象或选择强制转换,则不能使用任何标记为explicit的构造函数。这就解释了你在测试#9和#10之间看到的令人困惑的结果。

您可以编写一个特性来报告一个类型是否是Derived2<>:的专业化

template<typename T>
struct is_derived2 : std::false_type { };
template<class P1, class P2, class P>
struct is_derived2<Derived2<P1, P2, P>> : std::true_type { };

以及在Base<P>:中提取P的函数存根

template<typename Parameter>
Parameter base_parameter(Base<Parameter> const&);

然后将隐式构造函数更改为:

template<
    class T,
    class = typename std::enable_if<
        !is_derived2<T>::value
        && std::is_base_of<
            Base<decltype(base_parameter(std::declval<T>()))>,
            T
        >::value
    >::type
>
Derived2(const T& x);

在线演示:http://liveworkspace.org/code/c43d656d60f85b8b9d55d8e3c4812e2b


更新:这是一个在线演示,将这些更改合并到您的"编辑2"链接中:
http://liveworkspace.org/code/3decc7e0658cfd182e2f56f7b6cafe61

好吧,也许我找到了一个只意味着添加"伪"构造函数的变通方法:

#include <iostream>
#include <type_traits>
template<typename Type> class Base {};
template<typename Type> class Other : public Base<Type> {};
template<typename Type> class Derived : public Base<Type>
{
    public:
        Derived() {std::cout<<"empty"<<std::endl;}
        Derived(const Derived<Type>& x) {std::cout<<"copy"<<std::endl;}
        template<typename OtherType> explicit Derived(const Derived<OtherType>& x) {std::cout<<"explicit"<<std::endl;}
        template<typename Something> Derived(const Something& x) {std::cout<<"implicit"<<std::endl;}
    // Workaround
    public:
        template<template<typename> class Something, typename OtherType,
        class = typename std::enable_if< std::is_same< Something<OtherType>, Derived<OtherType> >::value>::type >
        Derived(const Something<OtherType>& x)
        {std::cout<<"workaround (for example always false static assert here)"<<std::endl;}
};
template<unsigned int Size> class Test {};
int main()
{
    Other<int> other0;
    Other<double> other1;
    Test<3> test;
    std::cout<<"1 = ";
    Derived<int> dint1;                     // <- empty
    std::cout<<"2 = ";
    Derived<int> dint2;                     // <- empty
    std::cout<<"3 = ";
    Derived<double> ddouble;                // <- empty
    std::cout<<"4 = ";
    Derived<double> ddouble1(ddouble);      // <- copy
    std::cout<<"5 = ";
    Derived<double> ddouble2(dint1);        // <- explicit
    std::cout<<"6 = ";
    ddouble = other0;                       // <- implicit
    std::cout<<"7 = ";
    ddouble = other1;                       // <- implicit
    std::cout<<"8 = ";
    ddouble = ddouble2;                     // <- nothing (normal : default assignment)
    std::cout<<"n9 = ";
    ddouble = Derived<double>(dint1);       // <- explicit
    std::cout<<"10 = ";
    ddouble = dint2;                        // <- workaround
    std::cout<<"11 = ";
    ddouble = test;                         // <- implicit
    return 0;
}

@大家:你认为这是解决那个问题的好办法吗?

LWS:http://liveworkspace.org/code/f581356a7472c902b10ca486d648fafc