为什么 std::packaged_task 的转换构造函数是显式的?

Why is converting constructor of std::packaged_task explicit?

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

例如,这会在将 lambda 作为参数传递给具有 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();
}

考虑以下示例。让我们创建一个模板 class,它使用非显式模板化转换构造函数模拟 class。

#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 

模板劫持了两种情况下的控制!在这种情况下,您实际上不能使用 copy\move 构造函数。避免它的最简单方法是明确转换。

#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 执行此操作,则转换构造函数可能会做错事情或可能无法编译,因为它无法使用自己的实例 [=34] =].看起来像复制的语句(但实际上是..移动?赋值?)是可能的。