Function that accepts both lvalue and rvalue arguments

后端 未结 6 1078
长发绾君心
长发绾君心 2020-12-08 13:13

Is there a way to write a function in C++ that accepts both lvalue and rvalue arguments, without making it a template?

For example, suppose I write a function

6条回答
  •  离开以前
    2020-12-08 14:07

    Here's a solution that scales to any number of parameters and doesn't require the accepting function to be a template.

    #include 
    
    template 
    struct lvalue_or_rvalue {
    
        Ref &&ref;
    
        template 
        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 
    #include 
    
    using namespace std;
    
    void print_stream(lvalue_or_rvalue is) {
        cout << is->rdbuf();
    }
    
    int main() {
        ifstream file("filename");
        print_stream(file); // call with lvalue
        print_stream(ifstream("filename")); // call with rvalue
        return 0;
    }
    

    I prefer this solution to the others because it's idiomatic, it doesn't require writing a function template every time you want to use it, and it produces sensible compiler errors, such as…

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

    The icing on the cake is that this solution also supports the implicit application of user-defined conversion constructors and conversion operators…

    #include 
    
    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 iw) {
        iw->value *= iw->value;
    }
    
    static void cube(lvalue_or_rvalue lw) {
        lw->value *= lw->value * lw->value;
    }
    
    static void square_root(lvalue_or_rvalue 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));
    }
    

提交回复
热议问题