2017-08-06 22 views
15

Tôi đang chơi với std::variant, lambdasstd::future và có kết quả siêu lạ khi tôi cố gắng kết hợp chúng lại với nhau. Dưới đây là ví dụ:Không thể khởi tạo std :: biến thể với các biểu thức lambda khác nhau

using variant_t = std::variant< 
    std::function<std::future<void>(int)>, 
    std::function<void(int)> 
>; 
auto f1 = [](int) { return std::async([] { return 1; }); }; 
auto f2 = [](int) { return std::async([] { }); }; 

variant_t v1(std::move(f1)); // !!! why DOES this one compile when it SHOULDN'T? 
auto idx1 = v1.index(); //equals 1. WHY? 

variant_t v2(std::move(f2)); // !!! why DOESN'T this one compile when it SHOULD? 

Đây là lỗi biên dịch:

Error C2665 'std::variant<std::function<std::future<void> (int)>,std::function<void (int)>>::variant': none of the 2 overloads could convert all the argument types

OK, cho phép thay đổi variant 's mục chữ ký từ trở void để int:

using variant_t = std::variant< 
    std::function<std::future<int>(int)>, 
    std::function<int(int)> 
>; 

variant_t v1(std::move(f1)); // COMPILES (like it should) 
auto idx1 = v1.index(); // equals 0 

variant_t v2(std::move(f2)); // DOESN'T compile (like it should) 

Cái quái gì thế đang diễn ra ở đây? Tại sao là std::future<void> rất đặc biệt?

+1

Lưu ý: 'std :: variant' là tính năng C++ 17, không phải C++ 11. – Rakete1111

+1

Lỗi biên dịch của bạn là từ ví dụ thứ hai, nhưng câu hỏi của bạn làm cho nó có vẻ giống như nó từ lần đầu tiên của bạn. – Barry

Trả lời

16

variant mẫu trình tạo chuyển đổi của người dùng sử dụng độ phân giải quá tải để xác định loại đối tượng được tạo nên có. Đặc biệt, điều này có nghĩa rằng nếu các chuyển đổi cho các loại đó đều tốt, thì hàm tạo không hoạt động; trong trường hợp của bạn, nó hoạt động iff chính xác một trong các chuyên ngành std::function là constructible từ đối số của bạn.

Vì vậy, khi nào là function<...> có thể định hình từ một đối số đã cho? Kể từ C++ 14, nếu đối số có thể gọi với các kiểu tham số và sinh ra một kiểu là convertible to the return type. Lưu ý rằng theo đặc điểm kỹ thuật này, nếu kiểu trả về là void, mọi thứ sẽ biến mất (như any expression can be converted to void with static_cast). Nếu bạn có số function trả lại void, hàm functor bạn chuyển vào có thể trả về bất cứ điều gì — đó là một tính năng, không phải lỗi! Đây cũng là lý do tại sao function<void(int)> được áp dụng cho f1. Mặt khác, future<int> không chuyển đổi thành future<void>; vì thế chỉ function<void(int)> là khả thi, và chỉ số của biến thể là 1.

Tuy nhiên, trong trường hợp thứ hai, lambda trả future<void>, đó là chuyển đổi cho cả hai future<void>void. Như đã đề cập ở trên, điều này làm cho cả hai chuyên ngành function khả thi, đó là lý do tại sao các variant không thể quyết định cái nào để xây dựng.

Cuối cùng, nếu bạn điều chỉnh loại trả về thành int, toàn bộ void vấn đề chuyển đổi này sẽ tránh được, vì vậy mọi thứ hoạt động như mong đợi.

+0

cảm ơn câu trả lời của bạn. Nhưng tôi vẫn không thể tưởng tượng như thế nào 'std :: tương lai ' có thể được chuyển đổi hoàn toàn để 'void' (hoặc' void' để 'std :: tương lai ' - Tôi không chắc tôi hiểu điểm này)? Nó có nghĩa là những gì everithing có thể chuyển đổi thành 'void'? –

+3

@DmitryKatkevich Điều đó là đúng, bất kỳ biểu thức nào cũng có thể được chuyển đổi thành 'void' qua' static_cast'. Nếu không, chúng tôi không thể cung cấp functors cho 'function' trả về một cái gì đó khi' function' mang lại 'void', đó là một tính năng mong muốn! – Columbo

Các vấn đề liên quan