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
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));
}