Std :: tie hoạt động như thế nào?


120

Tôi đã sử dụng std::tiemà không cần suy nghĩ nhiều về nó. Nó hoạt động nên tôi chỉ chấp nhận rằng:

auto test()
{
   int a, b;
   std::tie(a, b) = std::make_tuple(2, 3);
   // a is now 2, b is now 3
   return a + b; // 5
}

Nhưng ma thuật đen này hoạt động như thế nào? Làm thế nào một tạm thời được tạo ra bởi std::tiesự thay đổi ab? Tôi thấy điều này thú vị hơn vì nó là một tính năng thư viện, không phải một tính năng ngôn ngữ, vì vậy chắc chắn nó là thứ chúng ta có thể tự triển khai và hiểu được.

Câu trả lời:


152

Để làm rõ khái niệm cốt lõi, chúng ta hãy giảm nó thành một ví dụ cơ bản hơn. Mặc dù std::tierất hữu ích cho các hàm trả về (nhiều) giá trị hơn, nhưng chúng ta có thể hiểu nó tốt chỉ với một giá trị:

int a;
std::tie(a) = std::make_tuple(24);
return a; // 24

Những điều chúng ta cần biết để tiếp tục:

  • std::tie tạo và trả về một bộ tham chiếu.
  • std::tuple<int>std::tuple<int&>là 2 lớp hoàn toàn khác nhau, không có kết nối giữa chúng, khác là chúng được tạo từ cùng một mẫu std::tuple,.
  • tuple có một bộ operator=chấp nhận các loại khác nhau (nhưng cùng một số), trong đó mỗi thành viên được chỉ định riêng lẻ — từ cppreference :

    template< class... UTypes >
    tuple& operator=( const tuple<UTypes...>& other );

    (3) Đối với tất cả i, gán std::get<i>(other)cho std::get<i>(*this).

Bước tiếp theo là loại bỏ những hàm chỉ cản trở bạn, vì vậy chúng tôi có thể chuyển đổi mã của mình thành như sau:

int a;
std::tuple<int&>{a} = std::tuple<int>{24};
return a; // 24

Bước tiếp theo là xem chính xác điều gì xảy ra bên trong các cấu trúc đó. Đối với điều này, tôi tạo ra 2 loại Tnhóm thế cho std::tuple<int>Trnhóm thế std::tuple<int&>, được loại bỏ đến mức tối thiểu nhất cho các hoạt động của chúng tôi:

struct T { // substituent for std::tuple<int>
    int x;
};

struct Tr { // substituent for std::tuple<int&>
    int& xr;

    auto operator=(const T& other)
    {
       // std::get<I>(*this) = std::get<I>(other);
       xr = other.x;
    }
};

auto foo()
{
    int a;
    Tr{a} = T{24};

    return a; // 24
}

Và cuối cùng, tôi muốn loại bỏ tất cả các cấu trúc cùng nhau (tốt, nó không tương đương 100%, nhưng nó đủ gần với chúng tôi và đủ rõ ràng để cho phép nó):

auto foo()
{
    int a;

    { // block substituent for temporary variables

    // Tr{a}
    int& tr_xr = a;

    // T{24}
    int t_x = 24;

    // = (asignement)
    tr_xr = t_x;
    }

    return a; // 24
}

Vì vậy, về cơ bản, std::tie(a)khởi tạo một tham chiếu thành viên dữ liệu tới a. std::tuple<int>(24)tạo một thành viên dữ liệu có giá trị 24và phép gán gán 24 cho tham chiếu thành viên dữ liệu trong cấu trúc đầu tiên. Nhưng vì thành viên dữ liệu đó là một tham chiếu bị ràng buộc a, về cơ bản điều đó sẽ gán 24cho a.


1
Điều khiến tôi khó chịu là chúng ta đang gọi toán tử gán cho một giá trị.
Adam Zahran

Trong câu trả lời này , nó nói rằng một vùng chứa không thể chứa một tham chiếu. Tại sao tuplecó thể giữ một tài liệu tham khảo?
nn0p

6
@ nn0p std::tuplekhông phải là vùng chứa, ít nhất là không phải trong thuật ngữ C ++, không giống với std::vectorvà những thứ thích. Ví dụ, bạn không thể lặp lại theo các cách thông thường trên một tuple vì nó chứa các loại đối tượng khác nhau.
bolov

@Adam tie (x, y) = make_pair (1,2); thực sự trở thành std :: tie (x, y) .operator = (std :: make_pair (1, 2)), đó là lý do tại sao "gán cho một rvalue" hoạt động XD
Ju Piece

30

Điều này không trả lời câu hỏi của bạn theo bất kỳ cách nào, nhưng hãy để tôi đăng nó vì C ++ 17 về cơ bản đã sẵn sàng (với hỗ trợ trình biên dịch), vì vậy trong khi tự hỏi cách hoạt động của những thứ lỗi thời, có lẽ nên xem xét hiện tại như thế nào và trong tương lai, phiên bản C ++ cũng hoạt động.

Với C ++ 17, bạn có thể dễ dàng std::tieủng hộ cái được gọi là ràng buộc có cấu trúc . Chúng hoạt động giống nhau (tốt, không giống nhau , nhưng chúng có cùng hiệu ứng ròng), mặc dù bạn cần nhập ít ký tự hơn, nó không cần hỗ trợ thư viện và bạn cũng có khả năng lấy tham chiếu, nếu điều đó xảy ra. bạn muốn gì.

(Lưu ý rằng trong C ++ 17 hàm tạo thực hiện việc suy diễn đối số, do đó make_tuplecũng trở nên thừa.)

int a, b;
std::tie(a, b) = std::make_tuple(2, 3);

// C++17
auto  [c, d] = std::make_tuple(4, 5);
auto  [e, f] = std::tuple(6, 7);
std::tuple t(8,9); auto& [g, h] = t; // not possible with std::tie

2
Nếu dòng cuối cùng biên dịch tôi hơi lo ngại. Có vẻ như ràng buộc một tham chiếu đến một tham chiếu tạm thời là bất hợp pháp.
Nir Friedman

3
@Neil Nó phải là tham chiếu rvalue hoặc tham chiếu const lvalue. Bạn không thể liên kết một tham chiếu giá trị với một giá trị prvalue (tạm thời). Mặc dù đây đã là một "phần mở rộng" trong MSVC cho các lứa tuổi.
Nir Friedman

1
Cũng có thể đáng nói rằng không giống như tie, các ràng buộc có cấu trúc có thể được sử dụng theo cách này trên các loại không phải là cấu trúc mặc định.
Dan

5
Vâng, std::tie()ít hữu ích hơn rất nhiều kể từ C ++ 17, trong đó các ràng buộc có cấu trúc thường vượt trội hơn, nhưng nó vẫn có các công dụng, bao gồm việc gán cho các biến hiện có (không phải mới được khai báo đồng thời) và thực hiện một cách ngắn gọn những việc khác như hoán đổi nhiều biến hoặc những thứ khác phải gán cho tài liệu tham khảo.
underscore_d
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.