Helpex - Trao đổi & giúp đỡ Đăng nhập
39

Có cách nào để viết một hàm trong C ++ chấp nhận cả đối số lvalue và rvalue mà không làm cho nó trở thành mẫu không?

Ví dụ: giả sử tôi viết một hàm print_streamđọc từ an istreamvà in dữ liệu đã đọc ra màn hình hoặc một cái gì đó.

Tôi nghĩ là hợp lý khi gọi print_streamnhư thế này:

fstream file{"filename"};
print_stream(file);

cũng như như thế này:

print_stream(fstream{"filename"});

Nhưng tôi phải khai báo print_streamnhư thế nào để cả hai đều sử dụng được?

Nếu tôi khai báo nó là

void print_stream(istream& is);

thì lần sử dụng thứ hai sẽ không biên dịch vì một giá trị rvalue sẽ không liên kết với một tham chiếu giá trị không const.

Nếu tôi khai báo nó là

void print_stream(istream&& is);

thì lần sử dụng đầu tiên sẽ không biên dịch bởi vì một lvalue sẽ không liên kết với một tham chiếu rvalue.

Nếu tôi khai báo nó là

void print_stream(const istream& is);

thì việc triển khai của hàm sẽ không biên dịch vì bạn không thể đọc từ a const istream.

Tôi không thể đặt hàm làm mẫu và sử dụng "tham chiếu chung", vì việc triển khai hàm cần được biên dịch riêng.

Tôi có thể cung cấp hai quá tải:

void print_stream(istream& is);
void print_stream(istream&& is);

và có lệnh gọi thứ hai là lệnh đầu tiên, nhưng điều đó có vẻ như rất nhiều bảng soạn sẵn không cần thiết, và tôi sẽ thấy rất đáng tiếc nếu phải thực hiện điều đó mỗi khi tôi viết một hàm với ngữ nghĩa như thế này.

Có điều gì tốt hơn tôi có thể làm không?

39 hữu ích 5 bình luận 9.1k xem chia sẻ
24

Tôi sẽ nói rằng không có nhiều lựa chọn lành mạnh nào khác ngoài việc cung cấp hai quá tải hoặc làm cho chức năng của bạn trở thành một khuôn mẫu.

Nếu bạn thực sự, thực sự cần một giải pháp thay thế (xấu xí), thì tôi đoán điều duy nhất (điên rồ) bạn có thể làm là để hàm của bạn chấp nhận a const&, với điều kiện trước nói rằng bạn không thể chuyển một đối tượng thuộc constloại đủ tiêu chuẩn sang nó (dù sao bạn cũng không muốn hỗ trợ điều đó). Sau đó, hàm sẽ được phép loại bỏ constsự tham chiếu.

Nhưng cá nhân tôi sẽ viết hai quá tải và xác định một trong các nghĩa của cái kia, vì vậy bạn sẽ sao chép khai báo, nhưng không phải định nghĩa:

void foo(X& x) 
{ 
    // Here goes the stuff... 
}

void foo(X&& x) { foo(x); }
24 hữu ích 3 bình luận chia sẻ
6

Một giải pháp thay thế khá xấu xí khác là tạo hàm làm mẫu và khởi tạo rõ ràng cả hai phiên bản:

template<typename T>
void print(T&&) { /* ... */ }

template void print<istream&>(istream&);
template void print<istream&&>(istream&&);

Điều này có thể được biên dịch riêng. Mã máy khách chỉ cần khai báo mẫu.

Tuy nhiên, tôi cá tính chỉ gắn bó với những gì Andy Prowl gợi ý.

6 hữu ích 2 bình luận chia sẻ
5
// Because of universal reference
// template function with && can catch rvalue and lvalue 
// We can use std::is_same to restrict T must be istream
// it's an alternative choice, and i think is's better than two overload functions
template <typename T>
typename std::enable_if<
  std::is_same<typename std::decay<T>::type, istream>::value
>::type
print(T&& t) {
  // you can get the real value type by forward
  // std::forward<T>(t)
}
5 hữu ích 0 bình luận chia sẻ
5

Hãy mạnh dạn , nắm lấy các hàm chuyển tiếp chung chung và đặt tên cho chúng thật hay.

template<typename Stream>
auto stream_meh_to(Stream&& s) 
->decltype(std::forward<Stream>(s) << std::string{/*   */}){
    return std::forward<Stream>(s) << std::string{"meh\n"};}

Lưu ý rằng điều này sẽ hoạt động với bất kỳ thứ gì có ý nghĩa để nó hoạt động, không chỉ ostreams. Đó là một điều tốt.

Nếu hàm được gọi với một đối số không có ý nghĩa, nó sẽ đơn giản bỏ qua định nghĩa này. Ngẫu nhiên, điều này hoạt động tốt hơn nếu thụt lề được đặt thành 4 khoảng trắng. :)


Điều này cũng giống như câu trả lời của Cube, ngoại trừ việc tôi đang nói rằng, khi có thể, sẽ thanh lịch hơn khi không kiểm tra các kiểu cụ thể và để lập trình chung thực hiện công việc của nó.

5 hữu ích 0 bình luận chia sẻ
3

Đây là một giải pháp có quy mô theo bất kỳ số lượng tham số nào và không yêu cầu hàm chấp nhận làm mẫu.

#include <utility>

template <typename Ref>
struct lvalue_or_rvalue {

    Ref &&ref;

    template <typename Arg>
    constexpr lvalue_or_rvalue(Arg &&arg) noexcept
        :   ref(std::move(arg))
    { }

    constexpr operator Ref& () const & noexcept { return ref; }
    constexpr operator Ref&& () const && noexcept { return std::move(ref); }
    constexpr Ref& operator*() const noexcept { return ref; }
    constexpr Ref* operator->() const noexcept { return &ref; }

};
#include <fstream>
#include <iostream>

using namespace std;

void print_stream(lvalue_or_rvalue<istream> is) {
    cout << is->rdbuf();
}

int main() {
    ifstream file("filename");
    print_stream(file); // call with lvalue
    print_stream(ifstream("filename")); // call with rvalue
    return 0;
}

Tôi thích giải pháp này hơn những giải pháp khác vì nó mang tính thành ngữ, nó không yêu cầu viết một mẫu hàm mỗi khi bạn muốn sử dụng và nó tạo ra các lỗi trình biên dịch hợp lý, chẳng hạn như…

    print_stream("filename"); // oops! forgot to construct an ifstream
test.cpp: In instantiation of 'constexpr lvalue_or_rvalue<Ref>::lvalue_or_rvalue(Arg&&) [with Arg = const char (&)[9]; Ref = std::basic_istream<char>]':
test.cpp:33:25:   required from here
test.cpp:10:23: error: invalid initialization of reference of type 'std::basic_istream<char>&&' from expression of type 'std::remove_reference<const char (&)[9]>::type' {aka 'const char [9]'}
   10 |   : ref(std::move(arg))
      |                       ^

Điểm nổi bật là giải pháp này cũng hỗ trợ ứng dụng ngầm của các công cụ tạo chuyển đổi do người dùng xác định và toán tử chuyển đổi…

#include <cmath>

struct IntWrapper {
    int value;
    constexpr IntWrapper(int value) noexcept : value(value) { }
};

struct DoubleWrapper {
    double value;
    constexpr DoubleWrapper(double value) noexcept : value(value) { }
};

struct LongWrapper {
    long value;
    constexpr LongWrapper(long value) noexcept : value(value) { }
    constexpr LongWrapper(const IntWrapper &iw) noexcept : value(iw.value) { }
    constexpr operator DoubleWrapper () const noexcept { return value; }
};

static void square(lvalue_or_rvalue<IntWrapper> iw) {
    iw->value *= iw->value;
}

static void cube(lvalue_or_rvalue<LongWrapper> lw) {
    lw->value *= lw->value * lw->value;
}

static void square_root(lvalue_or_rvalue<DoubleWrapper> dw) {
    dw->value = std::sqrt(dw->value);
}

void examples() {
    // implicit conversion from int to IntWrapper&& via constructor
    square(42);

    // implicit conversion from IntWrapper& to LongWrapper&& via constructor
    IntWrapper iw(42);
    cube(iw);

    // implicit conversion from IntWrapper&& to LongWrapper&& via constructor
    cube(IntWrapper(42));

    // implicit conversion from LongWrapper& to DoubleWrapper&& via operator
    LongWrapper lw(42);
    square_root(lw);

    // implicit conversion from LongWrapper&& to DoubleWrapper&& via operator
    square_root(LongWrapper(42));
}
3 hữu ích 2 bình luận chia sẻ
0

Nếu tôi mong đợi hàm có quyền sở hữu đối số của hàm, tôi có xu hướng đặt đối số dưới dạng một giá trị, sau đó di chuyển nó vào. Điều này không mong muốn nếu đối số đắt tiền để di chuyển (ví dụ: std :: array).

Một ví dụ điển hình là thiết lập thành viên chuỗi của đối tượng:

class Foo {
   private:
      std::string name;
   public:
      void set_name( std::string new_name ) { name = std::move(new_name); }
};

Với định nghĩa này của hàm, tôi có thể gọi đặt tên mà không có bản sao của đối tượng chuỗi:

Foo foo;
foo.set_name( std::string("John Doe") );
// or
std::string tmp_name("Jane Doe");
foo.set_name( std::move(tmp_name) );

Nhưng tôi có thể tạo một bản sao nó nếu tôi muốn giữ quyền sở hữu giá trị ban đầu:

std::string name_to_keep("John Doe");
foo.set_name( name_to_keep );

Phiên bản cuối cùng này sẽ có hành vi rất giống với việc truyền tham chiếu const và thực hiện gán bản sao:

class Foo {
   // ...
   public:
      void set_name( const std::string& new_name ) { name = new_name; }
};

Điều này đặc biệt hữu ích cho các nhà xây dựng.

0 hữu ích 0 bình luận chia sẻ
loading
Không tìm thấy câu trả lời bạn tìm kiếm? Duyệt qua các câu hỏi được gắn thẻ c++ function c++11 lvalue rvalue , hoặc hỏi câu hỏi của bạn.

Có thể bạn quan tâm

loading