2017-12-20 111 views
5

Các mã sau đây:Hành vi của std :: có được trên các tham chiếu trị giá rvalue nguy hiểm không?

#include <tuple> 

int main() 
{ 
    auto f = []() -> decltype (auto) 
    { 
    return std::get<0> (std::make_tuple (0)); 
    }; 

    return f(); 
} 

(im lặng) tạo mã với hành vi không xác định - các rvalue tạm thời được trả về bởi make_tuple được phổ biến qua std :: get <> và qua decltype (tự động) vào các kiểu trả về. Vì vậy, nó kết thúc trở lại một tham chiếu đến một tạm thời đã đi ra khỏi phạm vi. Xem tại đây https://godbolt.org/g/X1UhSw.

Bây giờ, bạn có thể cho rằng việc sử dụng decltype(auto) của tôi là do lỗi. Nhưng trong mã chung của tôi (nơi mà loại tuple có thể là std::tuple<Foo &>), tôi không muốn luôn tạo một bản sao. Tôi thực sự muốn trích xuất giá trị chính xác hoặc tham chiếu từ tuple.

Cảm giác của tôi là tình trạng quá tải này của std::get là nguy hiểm:

template< std::size_t I, class... Types > 
constexpr std::tuple_element_t<I, tuple<Types...> >&& 
get(tuple<Types...>&& t) noexcept; 

Trong khi tuyên truyền tài liệu tham khảo giá trị trái vào yếu tố tuple có lẽ là hợp lý, tôi không nghĩ rằng tổ chức để tham khảo rvalue.

Tôi chắc chắn rằng ủy ban tiêu chuẩn nghĩ điều này thông qua rất cẩn thận, nhưng bất cứ ai có thể giải thích cho tôi tại sao điều này được coi là lựa chọn tốt nhất?

+0

* "Tôi không nghĩ rằng giữ cho tham chiếu rvalue" * - Nó sẽ xảy ra nếu bạn 'forward_as_tuple'. Bạn muốn danh mục giá trị được giữ nguyên. Hoặc nếu bạn muốn trích xuất một giá trị từ một hàm trả về giá trị. – StoryTeller

+0

Cảm ơn, nhưng tôi không thực sự chắc chắn rằng nó đang bảo tồn danh mục giá trị. Ví dụ, nếu tôi có một hàm trả về một 'std :: tuple ' và tôi gọi 'std :: get <0>' trên nó, tôi mong đợi sẽ nhận được một 'int' đơn giản, không phải là' int && '. Bạn có thể cho tôi một ví dụ cụ thể nơi bạn muốn một cái gì đó khác không? –

+1

Không phải là quá tải của 'get' cần thiết cho các ràng buộc có cấu trúc hiệu quả? –

Trả lời

2

Hãy xem xét ví dụ sau:

void consume(foo&&); 

template <typename Tuple> 
void consume_tuple_first(Tuple&& t) 
{ 
    consume(std::get<0>(std::forward<Tuple>(t))); 
} 

int main() 
{ 
    consume_tuple_first(std::tuple{foo{}}); 
} 

Trong trường hợp này, chúng ta biết rằng std::tuple{foo{}} là tạm thời và nó sẽ sống cho toàn bộ thời gian của biểu thức consume_tuple_first(std::tuple{foo{}}).

Chúng tôi muốn tránh mọi bản sao và di chuyển không cần thiết, nhưng vẫn tuyên truyền tính thời gian của foo{} đến consume.

Cách duy nhất để thực hiện điều đó là nhờ có std::get trả về tham chiếu rvalue rvalue khi được gọi với phiên bản std::tuple tạm thời.

live example on wandbox


Thay đổi std::get<0>(std::forward<Tuple>(t)) để std::get<0>(t) tạo ra một lỗi biên dịch (như dự kiến) (on wandbox).


Có một lựa chọn get trả về bởi kết quả giá trị trong một động thái không cần thiết bổ sung:

template <typename Tuple> 
auto myget(Tuple&& t) 
{ 
    return std::get<0>(std::forward<Tuple>(t)); 
} 

template <typename Tuple> 
void consume_tuple_first(Tuple&& t) 
{ 
    consume(myget(std::forward<Tuple>(t))); 
} 

live example on wandbox


nhưng bất cứ ai có thể giải thích cho tôi tại sao điều này được coi là lựa chọn tốt nhất?

Vì nó cho phép mã chung tùy chọn liên tục tuyên truyền thời gian tham chiếu rvalue khi truy cập bộ dữ liệu. Việc thay thế trả về theo giá trị có thể dẫn đến các hoạt động di chuyển không cần thiết.

+0

Tôi hiểu rồi. Vì vậy, "safe_get" của tôi trả về loại không được trang trí sẽ gây ra một động thái phụ. IMHO đó sẽ là một cái giá đáng để trả tiền cho sự an toàn, nhưng tôi ít nhất có thể thấy nó gây tranh cãi. Rất cám ơn câu trả lời của bạn! –

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