Working around the C++ limitation on non-const references to temporaries
Asked Answered
M

5

3

I've got a C++ data-structure that is a required "scratchpad" for other computations. It's not long-lived, and it's not frequently used so not performance critical. However, it includes a random number generator amongst other updatable tracking fields, and while the actual value of the generator isn't important, it is important that the value is updated rather than copied and reused. This means that in general, objects of this class are passed by reference.

If an instance is only needed once, the most natural approach is to construct them whereever needed (perhaps using a factory method or a constructor), and then passing the scratchpad to the consuming method. Consumers' method signatures use pass by reference since they don't know this is the only use, but factory methods and constructors return by value - and you can't pass unnamed temporaries by reference.

Is there a way to avoid clogging the code with nasty temporary variables? I'd like to avoid things like the following:

scratchpad_t<typeX<typeY,potentially::messy>, typename T> useless_temp = factory(rng_parm);
xyz.initialize_computation(useless_temp);

I could make the scratchpad intrinsically mutable and just label all parameters const &, but that doesn't strike me as best-practice since it's misleading, and I can't do this for classes I don't fully control. Passing by rvalue reference would require adding overloads to all consumers of scratchpad, which kind of defeats the purpose - having clear and concise code.

Given the fact that performance is not critical (but code size and readability are), what's the best-practice approach to passing in such a scratchpad? Using C++0x features is OK if required but preferably C++03-only features should suffice.

Edit: To be clear, using a temporary is doable, it's just unfortunate clutter in code I'd like to avoid. If you never give the temporary a name, it's clearly only used once, and the fewer lines of code to read, the better. Also, in constructors' initializers, it's impossible to declare temporaries.

Moss answered 16/9, 2010 at 21:48 Comment(6)
Can you take the "scratchpad" by value instead of reference? That way it could be temporary and mutable.Shechem
Doing that implies making a copy - fine in the case above where the temporary variable is useless, but not so fine when I indeed need to reuse the variable; that would mean rerunning the rng with the same state, a bad idea.Moss
@Eamon Nerbonne: Potentially, only conceptually. In optimized builds in many compilers the parameter will be constructed 'in place'.Shechem
@Charles: I think what he means is sometimes he'll be using an actual object passed to several routines in sequence. In that case, he doesn't want copies made for each routine, they have to be passed by reference.Senna
What I mean is that in some places useless_temp is actually reused several times and is thus not useless. Passing by value in that scenario would mean reusing the same value multiple times, breaking the scratchpad. Think of the passing a random number generator by value - if you do that twice, both instances will produce precisely the same random number sequence, hardly desirable.Moss
Right, what Dennis said faster than I :-).Moss
B
4

While it is not okay to pass rvalues to functions accepting non-const references, it is okay to call member functions on rvalues, but the member function does not know how it was called. If you return a reference to the current object, you can convert rvalues to lvalues:

class scratchpad_t
{
    // ...

public:

    scratchpad_t& self()
    {
        return *this;
    }
};

void foo(scratchpad_t& r)
{
}

int main()
{
    foo(scratchpad_t().self());
}

Note how the call to self() yields an lvalue expression even though scratchpad_t is an rvalue.

Please correct me if I'm wrong, but Rvalue reference parameters don't accept lvalue references so using them would require adding overloads to all consumers of scratchpad, which is also unfortunate.

Well, you could use templates...

template <typename Scratch> void foo(Scratch&& scratchpad)
{
    // ...
}

If you call foo with an rvalue parameter, Scratch will be deduced to scratchpad_t, and thus Scratch&& will be scratchpad_t&&.

And if you call foo with an lvalue parameter, Scratch will be deduced to scratchpad_t&, and because of reference collapsing rules, Scratch&& will also be scratchpad_t&.

Note that the formal parameter scratchpad is a name and thus an lvalue, no matter if its type is an lvalue reference or an rvalue reference. If you want to pass scratchpad on to other functions, you don't need the template trick for those functions anymore, just use an lvalue reference parameter.

By the way, you do realize that the temporary scratchpad involved in xyz.initialize_computation(scratchpad_t(1, 2, 3)); will be destroyed as soon as initialize_computation is done, right? Storing the reference inside the xyz object for later user would be an extremely bad idea.

self() doesn't need to be a member method, it can be a templated function

Yes, that is also possible, although I would rename it to make the intention clearer:

template <typename T>
T& as_lvalue(T&& x)
{
    return x;
}
Beattie answered 16/9, 2010 at 22:30 Comment(2)
I realize that it will be immediately destroyed. The scratchpad just has a bunch of values and storage required for initialization. Templates are not an attractive option since they'd require expanding header file size quite a bit - and the point of doing this is to enhance readability. On the other hand the self() function sounds interesting...Moss
And this solves my problem: self() doesn't need to be a member method, it can be a templated function ala std::move, of course!Moss
U
4

Is the problem just that this:

scratchpad_t<typeX<typeY,potentially::messy>, typename T> useless_temp = factory(rng_parm);

is ugly? If so, then why not change it to this?:

auto useless_temp = factory(rng_parm);
Udale answered 16/9, 2010 at 21:58 Comment(8)
@PigBen: Because auto is not yet in the standard, and some of us don't have the luxury of being able to venture outside of the standard.Fineberg
Well, that's one reason, but I'd also just like to avoid the pointless line of code, and in a constructor's initializer list I really can't declare a temporary.Moss
@Billy: He said "Using C++0x features is OK if required." And if there's no other good solution, then it is required.Udale
@PigBen: That's why I didn't downvote. I did, however, answer the question you asked, which was "Why not just change it to this?" :)Fineberg
Anyhow, thanks for the reminder about auto - I'm looking forward towards making dumping C++03 support already :-).Moss
@Eamon: The problem with the initialization list (as the problem in general) is there for a reason. The restriction that you cannot bind rvalues to non-const (lvalue) references isn't a natural one which is hard to circumvent; it's a deliberately introduced restriction to prevent you from shooting your own foot. I'm struggling to come up with a sensible case where you want to bind an rvalue to an lvalue reference in an initialization list; so far I've come up blank.Prentiss
@sbi: Passing accumulators or generators by reference is necessary for correct functioning in general. In the specific case of an initializer list, you might want to pass such a generator - as created by a factory method or constructor - to the constructor of your superclass. Mutability is required to ensure internal consistency (e.g. don't generate the same ID twice), not because of the actual value of the temporary.Moss
To clarify, if you know that the value will only be used once, you can pass by value or rvalue reference, but in general the initializer doesn't know that, so that would require duplicating all the constructors to accept both lvalue references (general case) and values (or rvalue references), which is needless code duplication and makes overload resolution trickier.Moss
B
4

While it is not okay to pass rvalues to functions accepting non-const references, it is okay to call member functions on rvalues, but the member function does not know how it was called. If you return a reference to the current object, you can convert rvalues to lvalues:

class scratchpad_t
{
    // ...

public:

    scratchpad_t& self()
    {
        return *this;
    }
};

void foo(scratchpad_t& r)
{
}

int main()
{
    foo(scratchpad_t().self());
}

Note how the call to self() yields an lvalue expression even though scratchpad_t is an rvalue.

Please correct me if I'm wrong, but Rvalue reference parameters don't accept lvalue references so using them would require adding overloads to all consumers of scratchpad, which is also unfortunate.

Well, you could use templates...

template <typename Scratch> void foo(Scratch&& scratchpad)
{
    // ...
}

If you call foo with an rvalue parameter, Scratch will be deduced to scratchpad_t, and thus Scratch&& will be scratchpad_t&&.

And if you call foo with an lvalue parameter, Scratch will be deduced to scratchpad_t&, and because of reference collapsing rules, Scratch&& will also be scratchpad_t&.

Note that the formal parameter scratchpad is a name and thus an lvalue, no matter if its type is an lvalue reference or an rvalue reference. If you want to pass scratchpad on to other functions, you don't need the template trick for those functions anymore, just use an lvalue reference parameter.

By the way, you do realize that the temporary scratchpad involved in xyz.initialize_computation(scratchpad_t(1, 2, 3)); will be destroyed as soon as initialize_computation is done, right? Storing the reference inside the xyz object for later user would be an extremely bad idea.

self() doesn't need to be a member method, it can be a templated function

Yes, that is also possible, although I would rename it to make the intention clearer:

template <typename T>
T& as_lvalue(T&& x)
{
    return x;
}
Beattie answered 16/9, 2010 at 22:30 Comment(2)
I realize that it will be immediately destroyed. The scratchpad just has a bunch of values and storage required for initialization. Templates are not an attractive option since they'd require expanding header file size quite a bit - and the point of doing this is to enhance readability. On the other hand the self() function sounds interesting...Moss
And this solves my problem: self() doesn't need to be a member method, it can be a templated function ala std::move, of course!Moss
F
3

Personally, I would rather see const_cast than mutable. When I see mutable, I'm assuming someone's doing logical const-ness, and don't think much of it. const_cast however raises red flags, as code like this should.

One option would be to use something like shared_ptr (auto_ptr would work too depending on what factory is doing) and pass it by value, which avoids the copy cost and maintains only a single instance, yet can be passed in from your factory method.

Fineberg answered 16/9, 2010 at 22:15 Comment(5)
shared_ptr sounds reasonable, though that would mean using it throughout the API, which is perhaps more hassle than avoiding a temporary is worth. How would const_cast help?Moss
What's the official word on using const_cast on temporaries? My gut says that's UB, but I'm not sure. Practically speaking, I can't imagine it not working on complex types but I am curious about the legal repercussions.Senna
@Dennis: You don't use it on the temporary, you use it on members of the temporary.Fineberg
+1 for shared_ptr, auto_ptr, and you should have also pointed unique_ptr.Arlana
@Stephane: I never recommend use of libraries which are not possible to implement under the current standard. Unique_ptr is nice, but until move semantics are in the standard I'm hesitant to recommend ever using it.Fineberg
M
0

I marked FredOverflow's response as the answer for his suggestion to use a method to simply return a non-const reference; this works in C++03. That solution requires a member method per scratchpad-like type, but in C++0x we can also write that method more generally for any type:

template <typename T> T & temp(T && temporary_value) {return temporary_value;}

This function simply forwards normal lvalue references, and converts rvalue references into lvalue references. Of course, doing this returns a modifiable value whose result is ignored - which happens to be exactly what I want, but may seem odd in some contexts.

Moss answered 17/9, 2010 at 7:43 Comment(0)
G
0

If you allocate the object in the heap you might be able to convert the code to something like:

std::auto_ptr<scratch_t> create_scratch();

foo( *create_scratch() );

The factory creates and returns an auto_ptr instead of an object in the stack. The returned auto_ptr temporary will take ownership of the object, but you are allowed to call non-const methods on a temporary and you can dereference the pointer to get a real reference. At the next sequence point the smart pointer will be destroyed and the memory freed. If you need to pass the same scratch_t to different functions in a row you can just capture the smart pointer:

std::auto_ptr<scratch_t> s( create_scratch() );
foo( *s );
bar( *s );

This can be replaced with std::unique_ptr in the upcoming standard.

Graptolite answered 17/9, 2010 at 7:50 Comment(2)
The factory method might be a constructor, but otherwise this sounds like it'd work pretty much anytime; essentially, it's kin to FredOverflow's suggestion if implemented slightly differently: you use a method (here operator overload) to return a reference to bypass the usual restriction on rvalue references. In fact, you could replace auto_ptr with a plain wrapping struct and an implicit cast operator - right?Moss
Yes, they are implementation wise quite similar. The thing is that by providing a method that returns a reference or a cast operator you are playing a non-idiomatic trick to violate the restriction, while this approach is well known and idiomatic --at the cost of dynamic allocation of the object. This tries to follow the principle of least surpriseTangle

© 2022 - 2024 — McMap. All rights reserved.