首页 > 解决方案 > 为什么转换 std::packaged_task 的构造函数显式?

问题描述

为什么转换构造函数是std::packaged_task explicit,而同一个构造函数std::function不是?我找不到任何理由。

例如,这在将 lambda 作为参数传递给以 a packaged_task(或对它的引用)作为参数的函数时强制转换:

void f1(std::function<void()>);
std::future<void> f2(std::packaged_task<void()>);

int main()
{
  f1( []{ } );                                         // ok
  auto fut = f2( []{ } );                              // error
  auto fut = f2( (std::packaged_task<void()>) []{ } ); // ok
  fut.wait();
}

标签: c++constructorstd-functionexplicitpackaged-task

解决方案


考虑以下示例。让我们创建一个模板类来模拟具有非显式模板转换构造函数的类。

#include <iostream>

// Hypothetical overloaded constructor
template <class T>
struct Foo {
    template <class F>
    Foo(F&& f ) { std::cout << "Initialization of Foo \n"; }
    Foo(const Foo& ) { std::cout << "Copy of Foo\n"; }
    Foo(Foo&& ) { std::cout << "Move of Foo\n"; }
};

void bar( Foo<int> f ) {}

int main()
{
    int a = 0;
    std::cout << "1st case\n";
    bar(a);
    std::cout << "2nd case\n";
    bar(Foo<int>(a));   // incorrect behaviour
}

输出将是

1st case
Initialization of Foo 
2nd case
Initialization of Foo 

在这两种情况下,模板都会劫持控制权!在这种情况下,您实际上不能使用复制\移动构造函数。避免它的最简单方法是显式转换。

#include <iostream>

// Analog of standard's constructor idiom
template <class T>
struct Foo2 {
    
    template <class F>
    explicit Foo2(F&& f ) { std::cout << "Initialization of Foo2 \n"; }
    Foo2(const Foo2& ) { std::cout << "Copy of Foo2\n"; }
    Foo2(Foo2&& ) { std::cout << "Move of Foo2\n"; }
};


void bar2( Foo2<int> f ) {}

int main()
{
    int a = 0;
    
    Foo2<int> f{a};
    std::cout << "\nProper case 1\n";
    // bar2(a); - can't do that
    bar2(Foo2<int>(a));
    std::cout << "Proper case 2\n";
    bar2(f);
    return 0;
}

输出:

Initialization of Foo2 

Proper case 1
Initialization of Foo2 
Proper case 2
Copy of Foo2

std::function是可复制的,同时std::packaged_task必须定义自定义移动构造函数和删除复制构造函数。

在尝试传递std::function时,在这两种情况下都不会发生任何 BAD,复制和移动构造函数很可能对函数指针或对可调用对象的引用进行操作,std::function旨在对任何兼容的可调用对象(包括其自身)进行操作。

如果您尝试这样做 std::packaged_task,则转换构造函数可能会做错事或可能无法编译,因为它无法使用自己的类的实例。一个看起来像复制的语句(但实际上是.. 移动?赋值?)是可能的。


推荐阅读