Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

how do you write proxies for rvalues?

6 views
Skip to first unread message

Gene Bushuyev

unread,
Aug 17, 2010, 3:56:54 AM8/17/10
to
As an exercise to familiarize myself with rvalues I decided to write a
proxy class for plus operator. It turned out to be more difficult than
I thought.
Here is what I got (tell me if I did it wrong):

template<class T1, class T2>
class PlusProxy
{
T1 t1;
T2 t2;
public:
PlusProxy(T1&& t1, T2&& t2)
: t1(std::forward<T1>(t1)),
t2(std::forward<T1>(t2))
{}

PlusProxy(PlusProxy&& other)
: t1(std::forward<T1>(other.t1)),
t2(std::forward<T1>(other.t2))
{}

PlusProxy&& operator = (PlusProxy&& other)
{
swap(std::forward<T1>(*this), other);
}

friend
void swap(PlusProxy<T1,T2>&& p1, PlusProxy<T1,T2>&& p2)
{
swap(std::forward<T1>(p1.t1), std::forward<T1>(p2.t1));
swap(std::forward<T1>(p1.t2), std::forward<T1>(p2.t2));
}

decltype(typename std::remove_reference<T1>::type() +
typename std::remove_reference<T2>::type())
operator ()() const { return t1 + t2; }

//decltype(typename std::remove_reference<T1>::type()[0] +
// typename std::remove_reference<T2>::type()[0])
//operator [](std::size_t i) const { return t1[i] + t2[i]; }

};

template<class T1, class T2>
PlusProxy<T1, T2> MakePlus (T1&& t1, T2&& t2)
{
return PlusProxy<T1,T2>(std::forward<T1>(t1),
std::forward<T2>(t2));
}

int main()
{
int a = 1, b = 2;
std::cout << "int 1: " << MakePlus(a, b)() << std::endl;
std::cout << "int 2: " << MakePlus(a, 1)() << std::endl;
std::cout << "int 3: " << MakePlus(5, 2)() << std::endl;

std::string s1("-string1-");
std::string s2("-string2-");
std::cout << "str 1: " << MakePlus(s1, s2)() << std::endl;
std::cout << "str 2: "
<< MakePlus(s1, std::string("hello"))() << std::endl;
//std::cout << "str 3: "
// << MakePlus(s1, "world")() << std::endl;
//std::cout << "str 4: "
// << MakePlus("hello", "world")() << std::endl;
}

I compiled this example with VC2010 and gcc 4.5 and got the same
results.
The first problem I encountered was with operator[] in this example.
It turns out if I try to instantiate PlusProxy on int type, compiler
would complain about decltype(...) for operator[]. Why? There is no
instantiation of operator[], why doesn't compiler defer this check to
the point of instantiation as it would be the case if I used return
type without decltype.

The second problem was with instantiation on string types. The
expression MakePlus(s1, "world") wouldn't compile because
remove_reference did something to the string literal in decltype
expression, so I'm getting this error:
conversion from 'int' to non-scalar type 'const char [6]' requested.
Is there anything I can do to handle both string and non-string
literals in the same way?

--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Daniel Krügler

unread,
Aug 17, 2010, 1:37:12 PM8/17/10
to
On 17 Aug., 09:56, Gene Bushuyev <publicfil...@gbresearch.com> wrote:
> As an exercise to familiarize myself with rvalues I decided to write a
> proxy class for plus operator. It turned out to be more difficult than
> I thought.
> Here is what I got (tell me if I did it wrong):

In the following I assume that at least the headers <string>,
<iostream>, <utility>, and <type_traits> are included.

> template<class T1, class T2>
> class PlusProxy
> {
> T1 t1;
> T2 t2;
> public:
> PlusProxy(T1&& t1, T2&& t2)
> : t1(std::forward<T1>(t1)),
> t2(std::forward<T1>(t2))
> {}
>
> PlusProxy(PlusProxy&& other)
> : t1(std::forward<T1>(other.t1)),
> t2(std::forward<T1>(other.t2))
> {}
>
> PlusProxy&& operator = (PlusProxy&& other)
> {
> swap(std::forward<T1>(*this), other);
> }
>
> friend
> void swap(PlusProxy<T1,T2>&& p1, PlusProxy<T1,T2>&& p2)
> {
> swap(std::forward<T1>(p1.t1), std::forward<T1>(p2.t1));
> swap(std::forward<T1>(p1.t2), std::forward<T1>(p2.t2));
> }

Note that above operator= implementation will never consider
your above free swap function. This is so, because operator=
provides lvalues to swap, but your swap acts only on rvalues.
Either replace your above free swap by

friend void swap(PlusProxy<T1,T2>& p1, PlusProxy<T1,T2>& p2);

or add such an overload to enable proper swapping.

If a specialization of PlusProxy is instantiated, this will cause the
instantiation of all member *declarations*. But your last two member
declaration of operator ()() and operator[] can be ill-formed
depending
on the type.

To solve this problem, you need to make them constrained members.
This is possible in C++0x by taking advantage of default template
arguments in function templates. E.g. change

decltype(typename std::remove_reference<T1>::type()[0] +
typename std::remove_reference<T2>::type()[0])


operator [](std::size_t i) const { return t1[i] + t2[i]; }

to

template<class U = T1, class V = T2>
decltype(typename std::remove_reference<U>::type()[0] +
typename std::remove_reference<V>::type()[0])


operator [](std::size_t i) const { return t1[i] + t2[i]; }

and similarly for your operator() overload.

> The second problem was with instantiation on string types. The
> expression MakePlus(s1, "world") wouldn't compile because
> remove_reference did something to the string literal in decltype
> expression, so I'm getting this error:
> conversion from 'int' to non-scalar type 'const char [6]' requested.
> Is there anything I can do to handle both string and non-string
> literals in the same way?

Using std::remove_reference is a bad idea. In fact, you use it
as a work-around for a proper solution, which should use
std::declval. Given this tool, the recommended declarations
of both members could be

template<class U = T1, class V = T2>
decltype(std::declval<U>() + std::declval<V>())


operator ()() const { return t1 + t2; }

template<class U = T1, class V = T2>
decltype(std::declval<U>()[std::declval<std::size_t>()] +
std::declval<V>()[std::declval<std::size_t>()])


operator [](std::size_t i) const { return t1[i] + t2[i]; }

HTH & Greetings from Bremen,

Daniel Krügler

SG

unread,
Aug 17, 2010, 1:54:05 PM8/17/10
to
On 17 Aug., 09:56, Gene Bushuyev wrote:
> As an exercise to familiarize myself with rvalues I decided to write a
> proxy class for plus operator. It turned out to be more difficult than
> I thought.

Could you explain the purpose of your class template? Specifically, do
you really intent to store references to lvalue arguments in your
PlusProxy class?

> Here is what I got (tell me if I did it wrong):
>
> template<class T1, class T2>
> class PlusProxy
> {
> T1 t1;
> T2 t2;
> public:
> PlusProxy(T1&& t1, T2&& t2)
> : t1(std::forward<T1>(t1)),
> t2(std::forward<T1>(t2))
> {}
>
> PlusProxy(PlusProxy&& other)
> : t1(std::forward<T1>(other.t1)),
> t2(std::forward<T1>(other.t2))
> {}
>
> PlusProxy&& operator = (PlusProxy&& other)
> {
> swap(std::forward<T1>(*this), other);
> }

That can't be right. T1 is the wrong type here and you forgot a return
statement. The rvalue reference as return type seems fishy. It's
definitely unusual (and probably wrong depending on your goals).

> friend
> void swap(PlusProxy<T1,T2>&& p1, PlusProxy<T1,T2>&& p2)
> {
> swap(std::forward<T1>(p1.t1), std::forward<T1>(p2.t1));
> swap(std::forward<T1>(p1.t2), std::forward<T1>(p2.t2));
> }

Do you realize that this function only accepts rvalues and not lvalues
(since the reference binding rules update from almost two years ago)?

> decltype(typename std::remove_reference<T1>::type() +
> typename std::remove_reference<T2>::type())
> operator ()() const { return t1 + t2; }
>
> //decltype(typename std::remove_reference<T1>::type()[0] +
> // typename std::remove_reference<T2>::type()[0])
> //operator [](std::size_t i) const { return t1[i] + t2[i]; }
> };
>
> template<class T1, class T2>
> PlusProxy<T1, T2> MakePlus (T1&& t1, T2&& t2)
> {
> return PlusProxy<T1,T2>(std::forward<T1>(t1),
> std::forward<T2>(t2));
> }
>
> int main()
> {
> int a = 1, b = 2;
> std::cout << "int 1: " << MakePlus(a, b)() << std::endl;

Is it intentional that MakePlus returns a PlusProxy<int&,int&> object
in this case? Apparently, the proxy object stores a reference if the
argument to MakePlus was an lvalue and it stores the object directly
if the argument was an rvalue. Just saying because there is a slim
chance that this was unintentional.

> I compiled this example with VC2010 and gcc 4.5 and got the same
> results.
> The first problem I encountered was with operator[] in this example.
> It turns out if I try to instantiate PlusProxy on int type, compiler
> would complain about decltype(...) for operator[]. Why? There is no
> instantiation of operator[], why doesn't compiler defer this check to
> the point of instantiation as it would be the case if I used return
> type without decltype.

This is just a guess: Since you use the operator as part of the
function's signature (decltype for return type) and the function is
not a template member, the compiler probably tries to at least figure
out what kind of function that is (what return type it has) by
evaluating the decltype expression. Hence, the error.

> The second problem was with instantiation on string types. The
> expression MakePlus(s1, "world") wouldn't compile because
> remove_reference did something to the string literal in decltype
> expression, so I'm getting this error:
> conversion from 'int' to non-scalar type 'const char [6]' requested.
> Is there anything I can do to handle both string and non-string
> literals in the same way?
>

> decltype(typename std::remove_reference<T1>::type() +
> typename std::remove_reference<T2>::type())
> operator ()() const { return t1 + t2; }

It probably makes sense to write a typedef for this and use a helper
function like declval:

typedef decltype( declval<T1&>() + declval<T2&> ) result_type;

I'm exploiting reference collapsing and using lvalue references here
because 't1' and 't2' in your return expression are lvalues but T1 and
T2 might be object types in which case declval<T1>() would be an
rvalue. This is just to match exactly your return expression.

The declval template might not yet be part of your compiler's standard
library implementation. It looks approximately like this:

template<class T>
typename std::add_rvalue_reference<T> declval();

It's only supposed to be used in unevaluated contexts like decltype
which is why we don't need a definition for it.

Cheers!
SG

Daniel Krügler

unread,
Aug 18, 2010, 2:50:48 PM8/18/10
to
On 17 Aug., 19:54, SG <s.gesem...@gmail.com> wrote:
[..]

> I'm exploiting reference collapsing and using lvalue references here
> because 't1' and 't2' in your return expression are lvalues but T1 and
> T2 might be object types in which case declval<T1>() would be an
> rvalue. This is just to match exactly your return expression.

Good point. I overlooked the value-category within the context of
the member function, so your additional "add_lvalue_reference"
step is the proper way to do that.

Greetings from Bremen,

Daniel Krügler


0 new messages