5

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.)

Aaron McDaid
  • 26,501
  • 9
  • 66
  • 88
  • You say marking constructors explicit "helps but is not ideal" - what would the ideal solution be? Consider James Kanze's answer. – Tom Whittock Jan 25 '12 at 16:34
  • Non-`explicit` constructors have their uses. For example, when passing things by value, or else with things like `A a; B b = a;`. It's the specific case where changing `T&` to `const T&` does more than just ban modifications. It seems a little inconsistent that adding `const` to a reference does more than just ban modification. (I'll comment on all the answers sooner or later, they are all interesting.) – Aaron McDaid Jan 25 '12 at 17:35

6 Answers6

7

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.
}
Tom Whittock
  • 4,081
  • 19
  • 24
  • I bet you could put a type-dependent `static_assert` into the template func to catch it at compile time even. – Mark B Jan 25 '12 at 15:43
  • Looking good. I did further experiments with this answer. It appears that the defined function does not need to be a template. i.e. that the template function will attract all the invalid calls, and the non-template function can provide the valid implementation. Does this make sense? – Aaron McDaid Jan 25 '12 at 15:44
  • 1
    @Aaron McDaid You should (almost) never mix overloading with specialization for reasons that escape me right now, but it has to do with the compiler not always finding the function you wanted. – Mark B Jan 25 '12 at 15:47
  • Note that `single parameter constructors` isn't entirely accurate as a five parameter constructor with four default values can be called with a single parameter and is in fact a converting constructor that should also be marked explicit. – Mark B Jan 25 '12 at 15:48
3

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.

Xeo
  • 129,499
  • 52
  • 291
  • 397
2

This is pretty simple to solve: stop taking values by reference. If you want to ensure that a parameter is addressable, then make it an address:

void bar_const(const long *) { }

That way, the user must pass a pointer. And you can't get a pointer to a temporary (unless the user is being terribly malicious).

That being said, I think your thinking on this matter is... wrongheaded. It comes down to this point.

perhaps I will take it's address, not realizing that I am, in effect, taking the address of a temporary.

Taking the address of a const& that happens to be a temporary is actually fine. The problem is that you cannot store it long-term. Nor can you transfer ownership of it. After all, you got a const reference.

And that's part of the problem. If you take a const&, your interface is saying, "I'm allowed to use this object, but I do not own it, nor can I give ownership to someone else." Since you do not own the object, you cannot store it long-term. This is what const& means.

Taking a const* instead can be problematic. Why? Because you don't know where that pointer came from. Who owns this pointer? const& has a number of syntactic safeguards to prevent you from doing bad things (so long as you don't take its address). const* has nothing; you can copy that pointer to your heart's content. Your interface says nothing about whether you are allowed to own the object or transfer ownership to others.

This ambiguity is why C++11 has smart pointers like unique_ptr and shared_ptr. These pointers can describe real memory ownership relations.

If your function takes a unique_ptr by value, then you now own that object. If it takes a shared_ptr, then you now share ownership of that object. There are syntactic guarantees in place that ensure ownership (again, unless you take unpleasant steps).

In the event of your not using C++11, you should use Boost smart pointers to achieve similar effects.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
1

You can't, and even if you could, it probably wouldn't help much. Consider:

void another(long const& l)
{
    bar_const(l);
}

Even if you could somehow prevent the binding to a temporary as input to bar_const, functions like another could be called with the reference bound to a temporary, and you'd end up in the same situation.

If you can't accept a temporary, you'll need to use a reference to a non-const, or a pointer:

void bar_const(long const* l);

requires an lvalue to initialize it. Of course, a function like

void another(long const& l)
{
    bar_const(&l);
}

will still cause problems. But if you globally adopt the convention to use a pointer if object lifetime must extend beyond the end of the call, then hopefully the author of another will think about why he's taking the address, and avoid it.

James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • I think I would like a convention whereby `&` was only for the non-const parameters. So that I could read `bar(a,b,&c)` and see that `a` and `b` are input and `c` will store the output. Your answer suggests the opposite convention. – Aaron McDaid Jan 25 '12 at 17:36
  • @AaronMcDaid There are a number of different possible conventions. Basically, we have two alternatives, pointer and reference, but more than two distinctions worth making. Among the conventions I've seen are use references whenever possible, use pointers if the lifetime of the object must extend beyond the function call, and use pointers if the object will be modified; I'm sure that others are reasonable as well. The important thing is that everyone on the project uses the same convention. – James Kanze Jan 26 '12 at 08:45
0

I think your example with int and long is a bit of a red herring as in canonical C++ you will never pass builtin types by const reference anyway: You pass them by value or by non-const reference.

So let's assume instead that you have a large user defined class. In this case, if it's creating temporaries for you then that means you created implicit conversions for that class. All you have to do is mark all converting constructors (those that can be called with a single parameter) as explicit and the compiler will prevent those temporaries from being created automatically. For example:

class Foo
{
    explicit Foo(int bar) { }
};
Mark B
  • 95,107
  • 10
  • 109
  • 188
0

(Answering my own question thanks to this great answer on another question I asked. Thanks @hvd.)

In short, marking a function parameter as volatile means that it cannot be bound to an rvalue. (Can anybody nail down a standard quote for that? Temporaries can be bound to const&, but not to const volatile & apparently. This is what I get on g++-4.6.1. (Extra: see this extended comment stream for some gory details that are way over my head :-) ))

void foo( const volatile Input & input, Output & output) {
}

foo(input, output); // compiles. good
foo(get_input_as_value(), output); // compile failure, as desired.

But, you don't actually want the parameters to be volatile. So I've written a small wrapper to const_cast the volatile away. So the signature of foo becomes this instead:

void foo( const_lvalue<Input> input, Output & output) {
}

where the wrapper is:

template<typename T>
struct const_lvalue {
    const T * t;
    const_lvalue(const volatile T & t_) : t(const_cast<const T*>(&t_)) {}
    const T* operator-> () const { return t; }
};

This can be created from an lvalue only

Any downsides? It might mean that I accidentally misuse an object that is truly volatile, but then again I've never used volatile before in my life. So this is the right solution for me, I think.

I hope to get in the habit of doing this with all suitable parameters by default.

Demo on ideone

Community
  • 1
  • 1
Aaron McDaid
  • 26,501
  • 9
  • 66
  • 88