临时变量的 std::pair:为什么会调用复制构造函数而不是移动构造函数?

3

请考虑以下代码:

#include<iostream>
using namespace std;
struct A {
    A(const A&){
        cout<<"copy constructor"<<endl;
    }
    A(A&&){
        cout<<"move constructor"<<endl;
    }
    ~A(){
        cout<<"destructor"<<endl;
    }
    static std::pair<A,int> f1()
    {
        int i = 1;
        return std::pair<A,int>{i,2};
    }
    static std::pair<A,int> f2()
    {
        int i = 1;
        return std::pair<A,int>{A(i),2};
    }
    private:
    A(int){
        cout<<"constructor"<<endl;
    }
};

int main()
{
    cout<<"f1: "<<endl;
    A::f1();
    cout<<"f2: "<<endl;
    A::f2();
}

构造函数A(int)是私有的,因此pair<A,int>中的A不能从int直接构建。因此,在f1中会构造一个临时变量。在f2中,我明确地创建了临时变量,但行为不同,输出结果如下:
f1: 
constructor
copy constructor
destructor
destructor
f2: 
constructor
move constructor
destructor
destructor

我本期望在 A::f1 中也调用移动构造函数,但实际上调用了拷贝构造函数,这是次优的。为什么会出现这种情况?


最好将 A(int) 设为 explicit A(int) 而不是私有的。这样你就可以得到更好的输出 f1: \nconstructor\ndestructor - 273K
1个回答

5

如果您查看 pair的构造函数

有趣的构造函数是(2)和(3)

// (2)
constexpr pair( const T1& x, const T2& y );

// (3)
template< class U1 = T1, class U2 = T2 >
constexpr pair( U1&& x, U2&& y ); // SFINAE on constructible

注意,这里没有pair(T1&&, T2&&)

由于A(int)private的,std::is_constructible_v<A, int>false

因此,对于f1,只有(2)是可行的构造函数(即拷贝构造)。

对于f2,(3)是可行的(并且更匹配),因此进行了转发(即移动操作)。


如果我理解正确的话,在 f1 中,(3) 被排除作为可行的构造函数,然后使用隐式转换来构造 (2) 的临时对象。但是,如果构造了一个临时对象,那么 (2) 就会再次成为可行的构造函数,对吗(因为 std::is_constructible_v<A, A&&>true)?(3) 只是不再被考虑,因为它之前已经被排除了?这是一个语言设计问题,从理论上讲,可以通过在决定是否进行临时构造之后重新检查可行性来改进吗? - phinz
你的解释非常有道理,但我认为在排除模板实例化选项之前,可行性检查不考虑隐式转换是很奇怪的,特别是当隐式转换像这种情况(f1)一样最终被使用时。 - phinz
1
在一般情况下(const char*->std::string->Person->...),只需要进行一次通行更为简单。 - Jarod42
为什么没有 pair(T1&&, T2&&) 构造函数?是否有特定的原因? - phinz
你需要处理组合 (T1&&/const T2&)T1&&/U2&&U1/T2&& 等,以处理私有(非显式)构造函数... U1&&/U2&& 可以处理公共构造函数。 - Jarod42

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接