Lapys
Lapys

Reputation: 946

C++ - Pass by Reference/ Value using templates

Context

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…


Attempted Solution (Code)

#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?


Question

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

Answers (2)

Yksisarvinen
Yksisarvinen

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

ALX23z
ALX23z

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

Related Questions