Reputation: 946
When passing arguments to functions, we can either pass-by-reference or pass-by-value.
One advantage to passing by reference is that the argument does not need to be implemented via a copy of itself, and this can seem advantageous for custom objects that contain a lot of data.
But for multiple class interfaces, creating a pass-by-reference function for each interface can be… untidy e.g.:
void parse(Console&);
void parse(Dialog&);
void parse(Image&);
void parse(Window&);
void parse(...); // how many more overloads?!
So, on to templates to solve this untidy problem, right?
template <typename type> void parse(type&);
But I've hit a road bump…
#include <iostream>
class Object { public:
// Copy/ Move constructors implicitly defined;
// Same goes for the assignment operator.
constexpr inline Object(void) {}
// Test if the object can be a reference or is an expression value.
template <typename type>
inline static void parse(type) noexcept { std::cout << "[pass-by-value]" << std::endl; }
template <typename type>
inline static void parse(type&) noexcept { std::cout << "[pass-by-reference]" << std::endl; }
};
Passing the object by value works
Object::parse(Object()); // SUCCESS: "pass-by-value"
but using a reference causes the test function to conflict with its overloads.
Object object {};
Object::parse(object); // ERROR: Ambiguous call of `Object::parse` function.
// EXPECTED: "pass-by-reference"
I assume the call is ambiguous because the
parse(type)
overload is initialized with {type=Object&}
and the
parse(type&)
overload is initialized with {type=Object}
.
Although, I thought that the parse(type&)
overload would be preferred but apparently that doesn't seem to be the case.
So, what am I missing here?
How can we differentiate between an argument that could be a reference or an argument that is a constant expression using a template function?
Note: I'm trying to prevent unnecessary copies with user-defined objects i.e.:
If I pass Image() [type=Image]
as an argument, it will be passed by value.
Otherwise it will be passed by reference e.g.:
Image image; // -> image [type=Image&]
Image images[1]; // -> images[0] [type=Image&]
Image *image_p; // -> *(image_p + 0) [type=Image&]
Upvotes: 1
Views: 1218
Reputation: 22176
Basing on your comment, it seems that what you want is an overload on r-value reference (so that you can distinguish when temporary was passed or when existing object was passed)
void foo(int& x){
std::cout << x << " was an l-value\n";
}
void foo(int&& x){
std::cout << x << " was an r-value (possibly a temporary or std::move result)\n";
}
int main(){
int x = 5;
foo(x);
foo(10);
}
Note that second version does not perform a copy either. A temporary object is constructed and then move semantics are used.
If you don't want to modify the original object and don't care about distinction, you can simply make argument a const
reference
void foo(const int& x){
std::cout << x << " may or may not have been a temporary "
"(it's lifetime is prolonged until end of this function)\n";
}
int main(){
int x = 5;
foo(x);
foo(10);
}
No copies are needed here either.
Upvotes: 2
Reputation: 4703
This is a somewhat common problem.
You could deffirantiate the methods according to size of the object and whether it's trivially copyable. If it's not trivially copyable or too large use const reference - otherwise use copy.
This way you have no ambiguity to resolve - just utilize SFINEA to enable/disable certain functions.
Upvotes: 1