Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to take a parameter by const reference, while banning conversions so that temporaries aren't passed instead?

Tags:

c++

constants

Sometimes we like to take a large parameter by reference, and also to make the reference const if possible to advertize that it is an input parameter. But by making the reference const, the compiler then allows itself to convert data if it's of the wrong type. This means it's not as efficient, but more worrying is the fact that I think I am referring to the original data; perhaps I will take it's address, not realizing that I am, in effect, taking the address of a temporary.

The call to bar in this code fails. This is desirable, because the reference is not of the correct type. The call to bar_const is also of the wrong type, but it silently compiles. This is undesirable for me.

#include<vector>
using namespace std;

int vi;

void foo(int &) { }
void bar(long &) { }
void bar_const(const long &) { }

int main() {
   foo(vi);
   // bar(vi); // compiler error, as expected/desired
   bar_const(vi);
}

What's the safest way to pass a lightweight, read-only reference? I'm tempted to create a new reference-like template.

(Obviously, int and long are very small types. But I have been caught out with larger structures which can be converted to each other. I don't want this to silently happen when I'm taking a const reference. Sometimes, marking the constructors as explicit helps, but that is not ideal)

Update: I imagine a system like the following: Imagine having two functions X byVal(); and X& byRef(); and the following block of code:

 X x;
 const_lvalue_ref<X> a = x; // I want this to compile
 const_lvalue_ref<X> b = byVal(); // I want this to fail at compile time
 const_lvalue_ref<X> c = byRef(); // I want this to compile

That example is based on local variables, but I want it to also work with parameters. I want to get some sort of error message if I'm accidentally passing a ref-to-temporary or a ref-to-a-copy when I think I'll passing something lightweight such as a ref-to-lvalue. This is just a 'coding standard' thing - if I actually want to allow passing a ref to a temporary, then I'll use a straightforward const X&. (I'm finding this piece on Boost's FOREACH to be quite useful.)

like image 373
Aaron McDaid Avatar asked Jan 25 '12 15:01

Aaron McDaid


People also ask

Can a const reference be bound to a non const object?

No. A reference is simply an alias for an existing object. const is enforced by the compiler; it simply checks that you don't attempt to modify the object through the reference r .

Can a constant be passed by reference?

You only pass by reference to non-const if you want to change the arguments and have the client observe those changes. If you don't want to change the arguments, pass by reference to const or by value. If you want to change the arguments but have no effect on the client, pass by value.

Is it better to pass by reference or value?

Pass-by-references is more efficient than pass-by-value, because it does not copy the arguments. The formal parameter is an alias for the argument. When the called function read or write the formal parameter, it is actually read or write the argument itself.

Can we declare a non reference function argument const?

It cannot hurt to declare it const if you know that your function needs not modify its value during execution. Note that functions that change their arguments, when arguments are passed by value, should be rare. Declaring your variable const can prevent you from writing if (aIn = someValue) . Save this answer.


2 Answers

If you can use C++11 (or parts thereof), this is easy:

void f(BigObject const& bo){
  // ...
}

void f(BigObject&&) = delete; // or just undefined

Live example on Ideone.

This will work, because binding to an rvalue ref is preferred over binding to a reference-to-const for a temporary object.

You can also exploit the fact that only a single user-defined conversion is allowed in an implicit conversion sequence:

struct BigObjWrapper{
  BigObjWrapper(BigObject const& o)
    : object(o) {}

  BigObject const& object;
};

void f(BigObjWrapper wrap){
  BigObject const& bo = wrap.object;
  // ...
}

Live example on Ideone.

like image 26
Xeo Avatar answered Nov 08 '22 17:11

Xeo


Well, if your "large parameter" is a class, the first thing to do is ensure that you mark any single parameter constructors explicit (apart from the copy constructor):

class BigType
{
public:
    explicit BigType(int);
};

This applies to constructors which have default parameters which could potentially be called with a single argument, also.

Then it won't be automatically converted to since there are no implicit constructors for the compiler to use to do the conversion. You probably don't have any global conversion operators which make that type, but if you do, then

If that doesn't work for you, you could use some template magic, like:

template <typename T>
void func(const T &); // causes an undefined reference at link time.

template <>
void func(const BigType &v)
{
    // use v.
}
like image 61
Tom Whittock Avatar answered Nov 08 '22 17:11

Tom Whittock