2011-10-23 53 views
17

Tôi đang viết thư viện mạng và sử dụng nhiều ngữ nghĩa di chuyển để xử lý quyền sở hữu đối với các bộ mô tả tệp. Một trong những lớp học của tôi rất mong nhận được giấy gói mô tả tập tin các loại khác và mất quyền sở hữu, vì vậy nó là cái gì nhưLàm thế nào để tạo tham số tham chiếu rvalue mẫu CHỈ liên kết với tham chiếu rvalue?

struct OwnershipReceiver 
{ 
    template <typename T> 
    void receive_ownership(T&& t) 
    { 
    // taking file descriptor of t, and clear t 
    } 
}; 

Nó có để đối phó nhiều loại không liên quan nên receive_ownership có phải là một mẫu, và để an toàn, tôi muốn nó CHỈ liên kết với các tham chiếu rvalue, do đó người dùng phải khai báo rõ ràng std :: move khi truyền một giá trị.

receive_ownership(std::move(some_lvalue));

Nhưng vấn đề là: C++ template khấu trừ cho phép một giá trị trái để được thông qua trong mà không cần nỗ lực nhiều. Và tôi thực sự tự bắn vào chân một lần bằng cách vô tình chuyển một lvalue để receive_ownership và sử dụng lvalue (xóa) sau này.

Vì vậy, đây là câu hỏi: làm cách nào để tạo mẫu CHỈ liên kết với tham chiếu rvalue?

Trả lời

20

Bạn có thể hạn chế T đến không phải là một tài liệu tham khảo giá trị trái, và do đó ngăn ngừa lvalues ​​từ ràng buộc với nó:

#include <type_traits> 

struct OwnershipReceiver 
{ 
    template <typename T, 
      class = typename std::enable_if 
      < 
       !std::is_lvalue_reference<T>::value 
      >::type 
      > 
    void receive_ownership(T&& t) 
    { 
    // taking file descriptor of t, and clear t 
    } 
}; 

Nó cũng có thể là một ý tưởng tốt để thêm một số loại hạn chế để T như vậy mà chỉ nó chấp nhận trình bao bọc mô tả tệp.

+0

Bạn có thể sử dụng 'std: : is_rvalue_reference' thay vì – David

+0

Cảm ơn Howard, điều đó hoạt động tốt. Và Dave, tôi nghĩ chính xác giống như bạn làm lúc đầu, sau đó tôi thấy std :: is_rvalue_reference sẽ không hoạt động: nó không liên kết với một giá trị "thực", hoặc thậm chí std :: move() ed lvalue . –

+4

@Ralph: Bạn đã thử 'is_rvalue_reference :: value'? (Lưu ý '&&') – fredoverflow

3

Tôi sẽ cảm ơn Howard một lần nữa vì câu trả lời kịp thời và hữu ích, vấn đề của tôi đã được giải quyết.

Và trong suốt khóa học, tôi học được một cái gì đó mà mọi người dường như bị lẫn lộn khá thường xuyên: sử dụng SFINAE là OK, nhưng tôi không thể sử dụng

std::is_rvalue_reference<T>::value 

cách duy nhất nó làm việc như tôi muốn là

!std::is_lvalue_reference<T>::value 

Lý do là: Tôi cần chức năng của mình để nhận "giá trị" chứ không phải "tham chiếu rvalue". Một hàm SFINAE ed với std :: is_rvalue_reference :: giá trị sẽ không nhận được "rvalue", nhưng chỉ nhận "tham chiếu rvalue". (bỏ một quirk, huh?)

2

Đối với tham chiếu lvalue, T được suy ra là tham chiếu lvalue, và đối với tham chiếu rvalue, T được suy ra là không tham chiếu.

Vì vậy, nếu các chức năng liên kết với một tài liệu tham khảo rvalue, những gì được nhìn thấy ở phần cuối bởi trình biên dịch cho một loại T chắc chắn là:

std::is_rvalue_reference<T>::value

và không

std::is_rvalue_reference<T&&>::value

3

Một cách đơn giản là cung cấp một thành viên đã xóa bị xóa chấp nhận tham chiếu lvalue:

template<typename T> void receive_ownership(T&) = delete; 

Điều này sẽ luôn phù hợp hơn với đối số lvalue.


Nếu bạn có chức năng cần một số đối số, tất cả đều cần phải có giá trị, chúng tôi sẽ cần một số chức năng đã xóa. Trong tình huống này, chúng tôi có thể thích sử dụng SFINAE để ẩn chức năng khỏi bất kỳ đối số lvalue nào.

Một cách để làm điều này có thể là với C++ 17 và khái niệm TS:

#include <type_traits> 

template<typename T> 
void receive_ownership(T&& t) 
    requires !std::is_lvalue_reference<T>::value 
{ 
    // taking file descriptor of t, and clear t 
} 

hoặc

#include <type_traits> 

void receive_ownership(auto&& t) 
    requires std::is_rvalue_reference<decltype(t)>::value 
{ 
    // taking file descriptor of t, and clear t 
} 

Đi nhẹ hơn nữa, bạn có thể xác định một khái niệm mới của riêng bạn, có thể hữu ích nếu bạn muốn sử dụng lại hoặc chỉ để làm rõ thêm:

#include <type_traits> 

template<typename T> 
concept bool rvalue = std::is_rvalue_reference<T&&>::value; 


void receive_ownership(rvalue&& t) 
{ 
    // taking file descriptor of t, and clear t 
} 

Lưu ý: với GCC 6.1, bạn sẽ cần phải chuyển -fconcepts cho trình biên dịch vì đây là phần mở rộng cho C++ chứ không phải là phần cốt lõi của nó.

Chỉ cần cho đầy đủ, đây là xét nghiệm đơn giản của tôi:

#include <utility> 
int main() 
{ 
    int a = 0; 
    receive_ownership(a);  // error 
    receive_ownership(std::move(a)); // okay 

    const int b = 0; 
    receive_ownership(b);  // error 
    receive_ownership(std::move(b)); // allowed - but unwise 
} 
+1

Tôi không thể' template void receive_ownership (T & t) = xóa'? – Xeverous

+0

Vâng, nó hoạt động và đơn giản hơn - Tôi đã chỉnh sửa tương ứng. –

+0

Nhưng sau đó không nên là 'const T &' hoặc cả hai? – Xeverous

0

Thật không may, nó có vẻ như cố gắng ra is_rvalue_reference<TF> (nơi TF là loại một cách hoàn hảo-chuyển tiếp) không hoạt động tốt nếu bạn đang thực sự cố gắng để làm quá tải phân biệt giữa const T&T&& (ví dụ: sử dụng enable_if ở cả hai, một với is_rvalue_reference_v<TF> và khác với !is_rvalue_reference_V<TF>).

Giải pháp (mặc dù hacky) là để phân rã số được chuyển tiếp T, sau đó đặt các tình trạng quá tải trong vùng chứa nhận biết các loại này. Tạo this example:

Hup, tôi đã sai, chỉ cần quên nhìn vào câu trả lời của Toby (is_rvalue_reference<TF&&>) - mặc dù cũng rất lúng túng mà bạn có thể làm std::forward<TF>(...), nhưng tôi đoán đó là lý do tại sao decltype(arg) cũng làm việc.

Anywho, đây là những gì tôi đã sử dụng để gỡ lỗi: (1) sử dụng struct quá tải, (2) sử dụng séc sai cho is_rvalue_reference, và (3) việc kiểm tra chính xác:

/* 
Output: 

const T& (struct) 
const T& (sfinae) 
const T& (sfinae bad) 
--- 
const T& (struct) 
const T& (sfinae) 
const T& (sfinae bad) 
--- 
T&& (struct) 
T&& (sfinae) 
const T& (sfinae bad) 
--- 
T&& (struct) 
T&& (sfinae) 
const T& (sfinae bad) 
--- 
*/ 

#include <iostream> 
#include <type_traits> 

using namespace std; 

struct Value {}; 

template <typename T> 
struct greedy_struct { 
    static void run(const T&) { 
    cout << "const T& (struct)" << endl; 
    } 
    static void run(T&&) { 
    cout << "T&& (struct)" << endl; 
    } 
}; 

// Per Toby's answer. 
template <typename T> 
void greedy_sfinae(const T&) { 
    cout << "const T& (sfinae)" << endl; 
} 

template < 
    typename T, 
    typename = std::enable_if_t<std::is_rvalue_reference<T&&>::value>> 
void greedy_sfinae(T&&) { 
    cout << "T&& (sfinae)" << endl; 
} 

// Bad. 
template <typename T> 
void greedy_sfinae_bad(const T&) { 
    cout << "const T& (sfinae bad)" << endl; 
} 

template < 
    typename T, 
    typename = std::enable_if_t<std::is_rvalue_reference<T>::value>> 
void greedy_sfinae_bad(T&&) { 
    cout << "T&& (sfinae bad)" << endl; 
} 

template <typename TF> 
void greedy(TF&& value) { 
    using T = std::decay_t<TF>; 
    greedy_struct<T>::run(std::forward<TF>(value)); 
    greedy_sfinae(std::forward<TF>(value)); 
    greedy_sfinae_bad(std::forward<TF>(value)); 
    cout << "---" << endl; 
} 

int main() { 
    Value x; 
    const Value y; 

    greedy(x); 
    greedy(y); 
    greedy(Value{}); 
    greedy(std::move(x)); 

    return 0; 
} 
Các vấn đề liên quan