10

I have the following piece of code, as an example dec_proxy attempts to reverse the effects of the increment operator upon the type that is executed in a complex function call foo - which btw I cannot change the interface of.

#include <iostream>

template<typename T>
class dec_proxy
{
public:
   dec_proxy(T& t)
   :t_(t)
   {}

   dec_proxy<T>& operator++()
   {
      --t_;
      return *this;
   }

private:
   T& t_;
};

template<typename T, typename S, typename R>
void foo(T& t, S& s, R& r)
{
  ++t;
  ++s;
  ++r;
}

int main()
{
   int i = 0;
   double j = 0;
   short  k = 0;

   dec_proxy<int> dp1(i);
   dec_proxy<double> dp2(j);
   dec_proxy<short> dp3(k);

   foo(dp1,dp2,dp3);

   //foo(dec_proxy<int>(i),     <---- Gives an error
   //   dec_proxy<double>(j),     <---- Gives an error
   //   dec_proxy<short>(k));      <---- Gives an error 

   std::cout << "i=" << i << std::endl;

   return 0;
}

The problem is that for the various types I'd like to use dec_proxy I currently require creating a specialized instance of dec_proxy - it seems like a very messy and limited approach.

My question is: What is the correct way to pass such short-lived temporaries as non-const reference parameters?

Xander Tulip
  • 1,438
  • 2
  • 17
  • 32
  • Why not pass the object by value? – Konrad Rudolph Oct 05 '11 at 06:55
  • 1
    Because, that is how foo has been defined, I can't change foo and because foo takes multiple parameters. – Xander Tulip Oct 05 '11 at 06:58
  • 1
    In that case, excellent question. – Konrad Rudolph Oct 05 '11 at 06:59
  • 2
    Note that the last comment on http://stackoverflow.com/questions/1565600/how-come-a-non-const-reference-cannot-bind-to-a-temporary-object/1565811#1565811 says that your solution is invalid, because `dec_proxy_impl(t)` is destroyed as the function returns - in other words you're returning a reference a local. MSN's solution, however, works because the temporaries must persist until the end of the statement. A simple test program can verify this. – Adam Bowen Oct 05 '11 at 07:58
  • @AdamBowen: You're right, I've updated the post. – Xander Tulip Oct 05 '11 at 20:59

3 Answers3

14

Taking Stephen's advice, you should look at the answer to How come a non-const reference cannot bind to a temporary object? and simply add a member function that returns a reference dec_proxy, e.g.:

dec_proxy &ref() { return *this; }

and call foo:

foo(
    dec_proxy<int>(i).ref(), 
    dec_proxy<double>(j).ref(), 
    dec_proxy<short>(k).ref());

I'm pretty sure that compiles.

Community
  • 1
  • 1
MSN
  • 53,214
  • 7
  • 75
  • 105
7

Thanks to MSN, the solution:

I don't think it is correct by adding the function template template<typename T> dec_proxy_impl<T>& dec_proxy(T&t).

What it did is just cheating compiler. It will result in runtime error. The function foo requires the lvaue or lvalue reference. But template<typename T> dec_proxy_impl<T>& dec_proxy(T&t) doesn't return a valid lvalue reference. In the implementation, it creates a temporary object, and returns it. After the function call finishes, the temporary object will be destroyed. So the value reference passed into the function foo is wrong. Actually the referenced object has already been destroyed. The ++t;++s;++r are trying to access the invalid objects. The behavior is undefined.

The solution from MSN is correct. The life time of the object dec_proxy<int>(i) is from its declaration to the end of the function call. It makes sure the parameter in the function foo is valid.

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
Jeffrey
  • 4,436
  • 9
  • 38
  • 54
  • You’re right. I’m horrified that it’s even possible to cheat the compiler thus. Temporaries should have a separate type that cannot, by any means, be coerced to a non-temporary (this would render MSN’s solution kaputt as well but I could live with that, especially when there are rvalue references). – Konrad Rudolph Oct 05 '11 at 08:44
  • @KonradRudolph, temporaries can already have arbitrary code run on them; the issue (also mentioned in the answer I linked to) is whether you can do implicit operations on temporaries or not. If you are explicitly manipulating temporaries by calling member functions, that's fine (because it's explicit). – MSN Oct 05 '11 at 18:57
  • @MSN I know, but it subverts the type system. Ideally, a type system would forbid any illegal operation. Now, it’s trivial to prove that such a type system cannot be implemented at compile time (it would solve the halting problem). But I’d like the type system to be as strict as possible and it seems to me (at least at the first glance) that local reference scope leak could be statically checked in all circumstances. – Konrad Rudolph Oct 05 '11 at 20:06
2

What you try to do is to pass a rvalue (your new dec_facade<int>(i)) as an lvalue reference, which explains why it doesn't work.

If you compiler support it, you can use rvalue references, using && type modifier : (Support for rvalue reference could be enabled by switching on C++0x or C++11 [partial] support)

template<typename T>
void foo(T& t)
{    
    ++t;
}
template<typename T>
void foo(T&& t)
{    
    ++t;
}

But that only one part of the problem. What you try to do, is to pre-increment a temporary value! That's non-sense, as it won't live after that call. Your object will be increment, then destroyed.


One other solution would be to remove the & from your function definition, which would permit it to accept any parameter. But that's perhaps not what you want.

Jaffa
  • 12,442
  • 4
  • 49
  • 101
  • That is indeed a good solution for C++11, but i was hoping that perhaps there might also be a clean way to do it in C++03 et al. – Xander Tulip Oct 05 '11 at 07:05
  • 3
    No, it’s **not** nonsense, look at the code again. The façade just delegates the call along, so even though it itself is a temporary, the effect won’t be lost. – Konrad Rudolph Oct 05 '11 at 07:06
  • @KonradRudolph If the object is an rvalue, it will be incremented but you won't see any result as it would be destroyed at end of foo's scope no? – Jaffa Oct 05 '11 at 07:14
  • 3
    @Geoffroy But this is not what’s happening! *Just look at the code*. The value that gets incremented (well, decremented actually) is a local variable in `main` that is *not* a temporary. – Konrad Rudolph Oct 05 '11 at 07:27
  • In general it's a bad idea to pass a temporary to a function that expects a regular reference. It works for **this** function (the function from the question) because this function doesn't actually use the fact that the parameter is a regular reference. IMO, this function is incorrectly defined. But, of course, you have to work with what you have. – Max Lybbert Oct 05 '11 at 07:55