内部类中的通用复制构造函数

generalized copy constructor in an inner class

本文关键字:复制 构造函数 内部类      更新时间:2023-10-16

我想指定从A<T>::BA<U>::B的转换。

template<typename T>
struct A
{
    struct B
    {
        B() {}
        template<typename U>
        B(const typename A<U>::B& rhs) {}
    };
};
int main()
{
    A<int>::B x;
    A<double>::B y = x;
}

我认为这将做到这一点,但我得到编译器错误:

请求从' A::B '转换为非标量类型' A::B ' "

为什么我的代码不正确,实现所需转换的正确方法是什么?

模板不能是复制构造函数。§12.8/2,脚注:

因为模板构造函数永远不是复制构造函数,所以存在这样一个模板并不会抑制复制构造函数的隐式声明。模板构造函数与其他构造函数(包括复制构造函数)一起参与重载解析,如果模板构造函数比其他构造函数提供更好的匹配,则可以使用模板构造函数来复制对象。

由于模板的实参是const &,它的签名将与复制情况下隐式声明的函数完全相同,因此它永远不会用作复制构造函数。

这可能没有问题,因为在示例中您将其用作转换构造函数。然而,::之前的模板参数是一个非推导的上下文,因此编译器不能插入A<int>::B并解析int。由于专门化模板的方法多种多样,编译器无法确定哪个A(如果有的话)符合条件。您可以在A<float>中添加typedef A<int>::B B;,然后intfloat都将符合U

您可以通过使用SFINAE并向类添加成员类型来解决这个问题,以帮助导航层次结构。这是一个演示。

#include <typeinfo>
#include <iostream>
template<typename T>
struct A
{
    typedef T type;
    struct B
    {
        B() {}
        template<typename U>
        B(const U& rhs, typename U::nest_A_parent * = NULL ) {
            std::cout << "copied from type "
            << typeid( typename U::nest_A_parent::type ).name() << 'n';
        }
    private:
        typedef A nest_A_parent;
        template< typename U >
        friend struct B;
    };
};
int main()
{
    A<int>::B x;
    A<double>::B y( x );
}

稍微修改了源代码,以说明这是关于类型推导系统的限制:

template<typename T>
struct A
{
    struct B
    {
        B() {}
        template<typename U>
            B(const typename A<U>::B& rhs) {}
        template<typename U>
            B& operator=(const typename A<U>::B& rhs) {}
        template<typename U>
            B& something(const typename A<U>::B& rhs) {}
    };
};
int main()
{
    A<int>::B x;
    A<double>::B y(x);     // fails to deduce
    A<double>::B y = x;    // fails to deduce
    A<double>::B y; y = x; // fails to deduce
    x.something(y);        // fails to deduce
    x.something<double>(y);// NO PROBLEM
}

您可以看到,当我们稍微帮助编译器时,就没有更多的问题了。还要注意实际的编译器错误(gcc)显示了它的混乱:

test.cpp|24 col 15| note: candidate is:
test.cpp|15 col 44| note: template<class U> A<T>::B& A<T>::B::something(const typename A<U>::B&) 
 [with U = U, T = int, A<T>::B = A<int>::B, typename A<U>::B = A<T>::B]

注意U = U(即未解析)

如何做一个转换构造函数(正如Potatoswatter指出的,根据定义它不能是复制构造函数),它只匹配嵌套类型B,对于任何A<T>::B:

namespace detail {
// private type to identify all A<T>::B
struct B {};
} // detail
// trait to identify all A<T>::B
// a template alias could also be used here
template<typename T>
struct is_b: std::is_base_of<detail::B, T> {};
template<typename T>
struct A
{
    struct B: detail::B {
        B() {}
        template<typename U>
        B(U&& u)
        {
            static_assert( is_b<typename std::decay<U>::type>::value
                           , "Conversion only allowed from A<T>::B" );
        }
    };
};

该技术的优点是它不使用SFINAE,因此无效的转换尝试将通过static_assert报告,而不是无声地失败。另一方面,如果至少有一个其他模板转换构造函数,则需要SFINAE。

这是假设A<T>::B不同于A<U>::B(对于不同的TU)。如果嵌套类型是相同的(就像您简化的示例代码中的情况一样),您最好在其他地方定义B,并在A中使用typedef some_private_namespace::B B;