Verbosity when optimizing with rvalue references

S

Sousuke

The optimizations that rvalue references make possible are nice, but
I'm having one problem with them (rvalue refs): they sometimes lead to
too much verbosity.

When defining a constructor or a setter, you usually take a const T&
and assign it to one of the class's members. In C++0x, you can
additionally define an overload that takes a T&&:

class OneString
{
public:
OneString(const string& s) : m_s(s)
{
}

OneString(string&& s) : m_s(move(s))
{
}

private:
string m_s;
};

One additional overload is not too much verbosity, but see the two-
argument case:

class TwoStrings
{
public:
TwoStrings(const string& s1, const string& s2) : m_s1(s1),
m_s2(s2)
{
}

TwoStrings(string&& s1, const string& s2) : m_s1(move(s1)),
m_s2(s2)
{
}

TwoStrings(const string& s1, string&& s2) : m_s1(s1),
m_s2(move(s2))
{
}

TwoStrings(string&& s1, string&& s2) : m_s1(move(s1)),
m_s2(move(s2))
{
}

private:
string m_s1;
string m_s2;
};

I don't even know how many overloads would there be for 3 arguments
(27 maybe?).

Is there a way to avoid this verbosity?
 
A

Alf P. Steinbach /Usenet

* Sousuke, on 24.06.2010 17:23:
The optimizations that rvalue references make possible are nice, but
I'm having one problem with them (rvalue refs): they sometimes lead to
too much verbosity.

When defining a constructor or a setter, you usually take a const T&
and assign it to one of the class's members. In C++0x, you can
additionally define an overload that takes a T&&:

class OneString
{
public:
OneString(const string& s) : m_s(s)
{
}

OneString(string&& s) : m_s(move(s))
{
}

private:
string m_s;
};

One additional overload is not too much verbosity, but see the two-
argument case:

class TwoStrings
{
public:
TwoStrings(const string& s1, const string& s2) : m_s1(s1),
m_s2(s2)
{
}

TwoStrings(string&& s1, const string& s2) : m_s1(move(s1)),
m_s2(s2)
{
}

TwoStrings(const string& s1, string&& s2) : m_s1(s1),
m_s2(move(s2))
{
}

TwoStrings(string&& s1, string&& s2) : m_s1(move(s1)),
m_s2(move(s2))
{
}

private:
string m_s1;
string m_s2;
};

I don't even know how many overloads would there be for 3 arguments
(27 maybe?).

Is there a way to avoid this verbosity?

With the disclaimer that I haven't actually used C++0x rvalue references, why do
you want the ordinary reference overloads? I thought much of the point was that
an rvalue reference could deal with both cases. Isn't it so?


Cheers & hth.,

- Alf
 
S

Sousuke

Make the constructor a function template and use perfect forwarding perhaps?
Not ideal as it is no longer obvious as to what types the constructor takes.

Also, calling std::move on the parameters would make them into rvalues
even if the respective arguments were lvalues!
 
B

Bo Persson

Sousuke said:
[...]
TwoStrings(string&& s1, string&& s2) : m_s1(move(s1)),
m_s2(move(s2))
{
}
private:
string m_s1;
string m_s2;
};
I don't even know how many overloads would there be for 3
arguments (27 maybe?).
Is there a way to avoid this verbosity?

Make the constructor a function template and use perfect
forwarding perhaps? Not ideal as it is no longer obvious as to
what types the constructor takes.

Also, calling std::move on the parameters would make them into
rvalues even if the respective arguments were lvalues!

That's why we also have std:.forward to handle the template case. :)


Bo Persson
 
S

Sousuke

* Sousuke, on 24.06.2010 17:23:


















With the disclaimer that I haven't actually used C++0x rvalue references, why do
you want the ordinary reference overloads? I thought much of the point was that
an rvalue reference could deal with both cases. Isn't it so?

Well, rvalue references still respect const correctness. Besides, the
point of rvalue references is objects can be constructed/assigned to
by "stealing" from objects that are about to be destructed (rvalues),
so you need to know whether the source object is actually an rvalue
(because stealing from an object that is not about to be destructed
(lvalue) would be wrong). So I think the answer is no. But I'm still
learning about this and it's confusing :)
 
B

Bo Persson

Alf said:
* Sousuke, on 24.06.2010 17:23:

With the disclaimer that I haven't actually used C++0x rvalue
references, why do you want the ordinary reference overloads? I
thought much of the point was that an rvalue reference could deal
with both cases. Isn't it so?

No, it didn't turn out that way in the end. :-(

Just like temporaries just bind to const lvalue references, lvalues
will not bind to rvalue references. Only in the case where the
parameter type is a template, can you use the reference collapsing
rules to turn type& && into type&.


Bo Persson
 
S

SG

The optimizations that rvalue references make possible are nice, but
I'm having one problem with them (rvalue refs): they sometimes lead to
too much verbosity.

When defining a constructor or a setter, you usually take a const T&
and assign it to one of the class's members. In C++0x, you can
additionally define an overload that takes a T&&:

class OneString
{
public:
    OneString(const string& s) : m_s(s)
    {}
    OneString(string&& s) : m_s(move(s))
    {}
private:
    string m_s;
};

One additional overload is not too much verbosity, but see the two-
argument case:

In this particular instance (constructor, the members don't yet exist)
I'd simply take the strings by value. This also scales well for
multiple string parameters:

class ThreeStrings
{
public:
    ThreeStrings(string s1, string s2, string s3)
: m_s1(move(s1)), m_s2(move(s2)), m_s3(move(s3))
    {}
private:
    string m_s1;
string m_s2;
string m_s3;
};

This works perfectly because we know that std::string supports quick
moving and because m_s1, m_s2 and m_s3 don't yet exist:

argument was #copies #moves
---------------------------------------------
lvalue 1 1
rvalue 0 2 (or 1 if elided)

=> no unnecessary copies


You could also use pass-by-value for a "setter" method:

void setter_I(string s1, string s2, string s3)
{
m_s1 = move(s1);
m_s2 = move(s2);
m_s3 = move(s3);
}

or pass-by-ref-to-const:

void setter_II(string const& s1, string const& s2, string const& s3)
{
m_s1 = s1;
m_s2 = s2;
m_s3 = s3;
}

But in both cases this might involve some unnecessary copying. In the
first case one string member object might already have enough
resources allocated to hold the new string value. In the second case
you'll have unnecessary copies when the argument was an rvalue.

If you don't want this, the only feasible solution I know of (perfect
forwarding) looks like this:

#include <string>
#include <type_traits>
#include <utility>

#define REQUIRES(...) class=typename \
std::enable_if<(__VAR_ARGS__)>::type

[...]

class ThreeStrings {
[...]
template <class S1, class S2, class S3,
REQUIRES( std::is_convertible<S1,std::string>::value
&& std::is_convertible<S2,std::string>::value
&& std::is_convertible<S3,std::string>::value )>
void setter_III(S1 && s1, S2 && s2, S3 && s3)
{
m_s1 = std::forward<S1>(s1);
m_s2 = std::forward<S2>(s2);
m_s3 = std::forward<S3>(s3);
}
[...]
};
I don't even know how many overloads would there be for 3 arguments
(27 maybe?).

That would be 8. Though, still to high for my taste. :)
Is there a way to avoid this verbosity?

Try to use pass-by-value and rely on move-optimized types more often.
If for some reason pass-by-value is not an option, perfect forwarding
(see above) is a way to fight the exploding number of otherwise
necessary overloads.

Cheers!
SG
 
J

Juha Nieminen

Sousuke said:
The optimizations that rvalue references make possible are nice, but
I'm having one problem with them (rvalue refs): they sometimes lead to
too much verbosity.

If I have understood correctly, you can simply write functions taking
rvalue references only, and skip writing functions taking regular references.
If a function takes an rvalue reference and you call it with an lvalue, the
compiler will still do the right thing, and the function will effectively
work as if it was taking a regular reference instead.

(Although this is just speculation at this point. I haven't actually
studied in detail how these things work in C++0x. So don't quote me on this.
Someone please correct me if I'm wrong here. This is important info to know.)
 
P

Paul Bibbings

Alf P. Steinbach /Usenet said:
* Sousuke, on 24.06.2010 17:23:

With the disclaimer that I haven't actually used C++0x rvalue
references, why do you want the ordinary reference overloads? I
thought much of the point was that an rvalue reference could deal with
both cases. Isn't it so?

It isn't. Rvalue references add two more implicit definitions to the
C++03 set, which was:

class A {
public:
A(); // ctor
A(const A&); // copy ctor
A& operator=(const A&); // copy assignment
~A(); // destructor
};

so that, in C++0x, you have:

class A {
public:
A(); // ctor
A(const A&); // copy ctor
A(A&&); // move constructor
A& operator=(const A&); // copy assignment
A& operator=(A&&); // move assignment
~A(); // destructor
};

This means that, even if you don't provide the copy constructor or copy
assignment operator, they will be compiler generated as before and will
be the preferred call for lvalues. In essence, the `point' of rvalue
references in this instance is to select the appropriate copy/move ctor
so that an appropriate application of copy/move semantics can be
controlled via the use of lvalue/rvalue refs.

You can see this in the following example:

/cygdrive/d/CPPProjects/CLCPP $cat move_ex.cpp
// file: move_ex.cpp

#include <iostream>
#include <utility>

class A {
public:
A() { std::cout << "ctor\n"; }
A(const A&) { std::cout << "copy ctor\n"; }
A(A&&) { std::cout << "move ctor\n"; } // line 10
A& operator=(const A&) { std::cout << "copy assign\n"; }
A& operator=(A&&) { std::cout << "move assign\n"; }
};

A f(const A& a_ref) {
std::cout << "void f(const A&)\n";
return a_ref;
}

A f(A&& a_rref) {
std::cout << "void f(A&&)\n";
return std::move(a_rref); // line 22
}

int main()
{
A a;
f(a);
f(A());
}

18:21:39 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP $gcc -std=c++0x -c move_ex.cpp

18:22:22 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP $g++ -static -o move_ex move_ex.o

18:22:33 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP $./move_ex
ctor
void f(const A&)
copy ctor
ctor
void f(A&&)
move ctor

In C++0x, of course, you can `delete' the move constructor and, if you
do that in the above code, you will see that it will fail for not being
available.

18:27:49 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP $gcc -std=c++0x -c move_ex.cpp
move_ex.cpp: In function ¡®A f(A&&)¡¯:
move_ex.cpp:10: error: deleted function ¡®A::A(A&&)¡¯
move_ex.cpp:22: error: used here

Regards

Paul Bibbings
 
P

Paul Bibbings

Alf P. Steinbach /Usenet said:
* Sousuke, on 24.06.2010 17:23:

With the disclaimer that I haven't actually used C++0x rvalue
references, why do you want the ordinary reference overloads? I
thought much of the point was that an rvalue reference could deal with
both cases. Isn't it so?

Of course, in my previous reply, I have *totally* missed the point since
what the OP is referring to are not move constructors at all!

Regards

Paul Bibbings
 
S

Sousuke

The optimizations that rvalue references make possible are nice, but
I'm having one problem with them (rvalue refs): they sometimes lead to
too much verbosity.
When defining a constructor or a setter, you usually take a const T&
and assign it to one of the class's members. In C++0x, you can
additionally define an overload that takes a T&&:
 class OneString
 {
 public:
    OneString(const string& s) : m_s(s)
    {}
    OneString(string&& s) : m_s(move(s))
    {}
 private:
    string m_s;
 };
One additional overload is not too much verbosity, but see the two-
argument case:

In this particular instance (constructor, the members don't yet exist)
I'd simply take the strings by value. This also scales well for
multiple string parameters:

  class ThreeStrings
  {
  public:
     ThreeStrings(string s1, string s2, string s3)
     : m_s1(move(s1)), m_s2(move(s2)), m_s3(move(s3))
     {}
  private:
     string m_s1;
     string m_s2;
     string m_s3;
  };

This works perfectly because we know that std::string supports quick
moving and because m_s1, m_s2 and m_s3 don't yet exist:

   argument was    #copies    #moves
   ---------------------------------------------
   lvalue          1          1
   rvalue          0          2 (or 1 if elided)

   =>  no unnecessary copies

You could also use pass-by-value for a "setter" method:

  void setter_I(string s1, string s2, string s3)
  {
    m_s1 = move(s1);
    m_s2 = move(s2);
    m_s3 = move(s3);
  }

or pass-by-ref-to-const:

  void setter_II(string const& s1, string const& s2, string const& s3)
  {
    m_s1 = s1;
    m_s2 = s2;
    m_s3 = s3;
  }

But in both cases this might involve some unnecessary copying. In the
first case one string member object might already have enough
resources allocated to hold the new string value. In the second case
you'll have unnecessary copies when the argument was an rvalue.

If you don't want this, the only feasible solution I know of (perfect
forwarding) looks like this:

  #include <string>
  #include <type_traits>
  #include <utility>

  #define REQUIRES(...) class=typename \
    std::enable_if<(__VAR_ARGS__)>::type

  [...]

  class ThreeStrings {
    [...]
    template <class S1, class S2, class S3,
    REQUIRES( std::is_convertible<S1,std::string>::value
           && std::is_convertible<S2,std::string>::value
           && std::is_convertible<S3,std::string>::value )>
    void setter_III(S1 && s1, S2 && s2, S3 && s3)
    {
      m_s1 = std::forward<S1>(s1);
      m_s2 = std::forward<S2>(s2);
      m_s3 = std::forward<S3>(s3);
    }
    [...]
  };

Scary, but it's the most efficient :( So I'm still unsure about
whether to use pass-by-value or perfect forwarding. Either way, it's
better than what we had before.

Thanks all.
 
P

Paul Bibbings

Sousuke said:
Well, rvalue references still respect const correctness. Besides, the
point of rvalue references is objects can be constructed/assigned to
by "stealing" from objects that are about to be destructed (rvalues),
so you need to know whether the source object is actually an rvalue
(because stealing from an object that is not about to be destructed
(lvalue) would be wrong). So I think the answer is no. But I'm still
learning about this and it's confusing :)

I think that this is the case. If you take the following example:

19:22:27 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP $cat move_ex2.cpp
// file: move_ex2.cpp

#include <iostream>
#include <utility>

class A {
public:
A() { std::cout << "ctor\n"; }
A(const A&) { std::cout << "copy\n"; }
A(A&&) { std::cout << "move\n"; }
};

class B {
public:
B(const A& a) : m_a(a) { }
B(A&& a) : m_a(std::move(a)) { }
private:
A m_a;
};

int main()
{
A a;
B b1(a);
}

19:22:33 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP $./move_ex2
ctor
copy

we can see that the construction of b1 from lvalue a uses the copy ctor
of A to initialize B::m_a. If, however, we comment out B(const A&) then
we get:

19:28:14 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP $./move_ex2
ctor
move

and I think (if I have analysed this correctly) that what we have done -
or what we would do with a `real' implementation - is pilferred the
lvalue a, which is therefore invalid after the initialization of b1.

Regards

Paul Bibbings
 
P

Paul Bibbings

Bo Persson said:
No, it didn't turn out that way in the end. :-(

Just like temporaries just bind to const lvalue references, lvalues
will not bind to rvalue references.

I think you will find that they will. Try:

void f(int&&) { }

int main()
{
int i = 0; // lvalue
f(i);
}

Regards

Paul Bibbings
 
P

Paul Bibbings

Paul Bibbings said:
I think you will find that they will. Try:

void f(int&&) { }

int main()
{
int i = 0; // lvalue
f(i);
}

Scratch that! I was using gcc-4.4.3 which accepts this. gcc-4.5.0,
however, correctly rejects its, as per Pete's confirmation as a `recent
change'.

Regards

Paul Bibbings
 
P

Paul Bibbings

Paul Bibbings said:
I think that this is the case. If you take the following example:

19:22:27 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP $cat move_ex2.cpp
// file: move_ex2.cpp

#include <iostream>
#include <utility>

class A {
public:
A() { std::cout << "ctor\n"; }
A(const A&) { std::cout << "copy\n"; }
A(A&&) { std::cout << "move\n"; }
};

class B {
public:
B(const A& a) : m_a(a) { }
B(A&& a) : m_a(std::move(a)) { }
private:
A m_a;
};

int main()
{
A a;
B b1(a);
}

19:22:33 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP $./move_ex2
ctor
copy

we can see that the construction of b1 from lvalue a uses the copy ctor
of A to initialize B::m_a. If, however, we comment out B(const A&) then
we get:

19:28:14 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP $./move_ex2
ctor
move

and I think (if I have analysed this correctly) that what we have done -
or what we would do with a `real' implementation - is pilferred the
lvalue a, which is therefore invalid after the initialization of b1.

Correction. The above was compiled using gcc-4.4.3 which *incorrectly*
permits the binding of an lvalue to an rvalue reference. As Pete
reminds me elsewhere in this thread, this is no longer possible, so the
outcome of the above "If, however, we comment out B(const A&)" is that
compilation fails. The lvalue is *not* used nor pilferred.

Regards

Paul Bibbings
 
P

Paul Bibbings

Sousuke said:
The optimizations that rvalue references make possible are nice, but
I'm having one problem with them (rvalue refs): they sometimes lead to
too much verbosity.

When defining a constructor or a setter, you usually take a const T&
and assign it to one of the class's members. In C++0x, you can
additionally define an overload that takes a T&&:

class OneString
{
public:
OneString(const string& s) : m_s(s)
{
}

OneString(string&& s) : m_s(move(s))
{
}

private:
string m_s;
};

One additional overload is not too much verbosity, but see the two-
argument case:

class TwoStrings
{
public:
TwoStrings(const string& s1, const string& s2) : m_s1(s1),
m_s2(s2)
{
}

TwoStrings(string&& s1, const string& s2) : m_s1(move(s1)),
m_s2(s2)
{
}

TwoStrings(const string& s1, string&& s2) : m_s1(s1),
m_s2(move(s2))
{
}

TwoStrings(string&& s1, string&& s2) : m_s1(move(s1)),
m_s2(move(s2))
{
}

private:
string m_s1;
string m_s2;
};

I don't even know how many overloads would there be for 3 arguments
(27 maybe?).

Is there a way to avoid this verbosity?

I've had another look at this and wonder if the following example might
offer something useful. I have not found a way to reproduce the
semantics of the multiple constructors exactly, but it's an idea, at
least. (The key differences that I have noticed are highlighted in the
analysis below.)

Consider:

// file: 2arg_ctor.cpp

#include <iostream>
#include <utility>

class A
{
public:
A(int i) { std::cout << "A::A(int)\n"; } // ctor
A(const A&) { std::cout << "A::A(const A&)\n"; } // copy ctor
A(A&&) { std::cout << "A::A(A&&)\n"; } // move ctor
};

class D
{
public:
template<typename T1, typename T2>
D(T1&& t1, T2&& t2)
: a1_(std::forward<T1>(t1))
, a2_(std::forward<T2>(t2))
{ print_constructor<T1, T2>(); }
private:
// print helper for outputting constructor specialization
template<typename T1, typename T2>
void print_constructor() const;
private:
A a1_;
A a2_;
};

// Specializations for printing constructor
template<>
void D::print_constructor<A, A>() const
{ std::cout << "D::D<A, A>(A&&, A&&)\n"; }

template<>
void D::print_constructor<A, A&>() const
{ std::cout << "D::D<A, A&>(A&&, A&)\n"; }

template<>
void D::print_constructor<A&, A>() const
{ std::cout << "D::D<A&, A>(A&, A&&)\n"; }

template<>
void D::print_constructor<A&, A&>() const
{ std::cout << "D::D<A&, A&>(A&, A&)\n"; }

template<>
void D::print_constructor<int, int>() const
{ std::cout << "D::D<int, int>(int&&, int&&)\n"; }

template<>
void D::print_constructor<int, int&>() const
{ std::cout << "D::D<int, int&>(int&&, int&)\n"; }

template<>
void D::print_constructor<A, const A&>() const
{ std::cout << "D::D<A, const A&>(A&&, const A&)\n"; }

int main()
{
std::cout << "0: Initialize lvalues (A)...\n";
A a1(1), a2(2);
const A a3(3);
std::cout << "\n1: Construct D with (lvalue, lvalue)...\n";
D d1(a1, a2);
std::cout << "\n2: Construct D with (lvalue, rvalue)...\n";
D d2(a1, A(2));
std::cout << "\n3: Construct D with (rvalue, lvalue)...\n";
D d3(A(1), a2);
std::cout << "\n4: Construct D with (rvalue, rvalue)...\n";
D d4(A(1), A(2));
std::cout << "\n5: Construct D with (rvalue, rvalue)!...\n";
D d5(1, 2);
std::cout << "\n6: Construct D with (rvalue, lvalue)...\n";
int i2 = 2;
D d6(1, i2);
std::cout << "\n7: Construct D with (rvalue, const lvalue)...\n";
D d7(A(1), a3);
std::cout << "\n...etc\n";
}

/**
* Compile:
* i686-pc-cygwin-g++-4.5.0 -std=c++0x -static -o 2arg_ctor
* 2arg_ctor.cpp
*
* Output:
* 0: Initialize lvalues (A)...
* A::A(int)
* A::A(int)
* A::A(int)
*
* 1: Construct D with (lvalue, lvalue)...
* A::A(const A&)
* A::A(const A&)
* D::D<A&, A&>(A&, A&)
*
* 2: Construct D with (lvalue, rvalue)...
* A::A(int)
* A::A(const A&)
* A::A(A&&)
* D::D<A&, A>(A&, A&&)
*
* 3: Construct D with (rvalue, lvalue)...
* A::A(int)
* A::A(A&&)
* A::A(const A&)
* D::D<A, A&>(A&&, A&)
*
* 4: Construct D with (rvalue, rvalue)...
* A::A(int)
* A::A(int)
* A::A(A&&)
* A::A(A&&)
* D::D<A, A>(A&&, A&&)
*
* 5: Construct D with (rvalue, rvalue)!...
* A::A(int)
* A::A(int)
* D::D<int, int>(int&&, int&&)
*
* 6: Construct D with (rvalue, lvalue)...
* A::A(int)
* A::A(int)
* D::D<int, int&>(int&&, int&)
*
* 7: Construct D with (rvalue, const lvalue)...
* A::A(int)
* A::A(int)
* A::A(A&&)
* A::A(const A&)
* D::D<A, const A&>(A&&, const A&)
*
* ...etc
*/


Here we can see that the single parameterized constructor handles all
combinations of lvalue/rvalue refs as given, including const lvalues.

If we replace the definition of class D (above) with one that provides
an overloaded constructor to cover all lvalue/rvalue pairs, so:

class D
{
public:
D(const A& a1, const A& a2)
: a1_(a1), a2_(a2)
{ std::cout << "D::D(const A&, const A&)\n"; }
D(const A& a1, A&& a2)
: a1_(a1), a2_(std::move(a2))
{ std::cout << "D::D(const A&, A&&)\n"; }
D(A&& a1, const A& a2)
: a1_(std::move(a1)), a2_(a2)
{ std::cout << "D::D(A&&, const A&)\n"; }
D(A&& a1, A&& a2)
: a1_(std::move(a1)), a2_(std::move(a2))
{ std::cout << "D::D(A&&, A&&)\n"; }
private:
A a1_;
A a2_;
};

(and remove the specializations for D::print_constructor, which are
not then needed) we can compare the output from the two examples in
terms of efficiency of the occurrence and number of copies/moves, etc.

=================================+============================
template ctor | overloaded ctor
=================================+============================
0: Initialize lvalues (A)...
---------------------------------+----------------------------
A::A(int) | A::A(int)
A::A(int) | A::A(int)
A::A(int) | A::A(int)
=================================+============================
1: Construct D with (lvalue, lvalue)...
---------------------------------+----------------------------
A::A(const A&) | A::A(const A&)
A::A(const A&) | A::A(const A&)
D::D<A&, A&>(A&, A&) | D::D(const A&, const A&)
=================================+============================
2: Construct D with (lvalue, rvalue)...
---------------------------------+----------------------------
A::A(int) | A::A(int)
A::A(const A&) | A::A(const A&)
A::A(A&&) | A::A(A&&)
D::D<A&, A>(A&, A&&) | D::D(const A&, A&&)
=================================+============================
3: Construct D with (rvalue, lvalue)...
---------------------------------+----------------------------
A::A(int) | A::A(int)
A::A(A&&) | A::A(A&&)
A::A(const A&) | A::A(const A&)
D::D<A, A&>(A&&, A&) | D::D(A&&, const A&)
=================================+============================
4: Construct D with (rvalue, rvalue)...
---------------------------------+----------------------------
A::A(int) | A::A(int)
A::A(int) | A::A(int)
A::A(A&&) | A::A(A&&)
A::A(A&&) | A::A(A&&)
D::D<A, A>(A&&, A&&) | D::D(A&&, A&&)
=================================+============================
5: Construct D with (rvalue, rvalue)!...
---------------------------------+----------------------------
A::A(int) | A::A(int)
A::A(int) | A::A(int)
| A::A(A&&)
| A::A(A&&)
D::D<int, int>(int&&, int&&) | D::D(A&&, A&&)
=================================+============================
6: Construct D with (rvalue, lvalue)...
---------------------------------+----------------------------
A::A(int) | A::A(int)
A::A(int) | A::A(int)
| A::A(A&&)
| A::A(A&&)
D::D<int, int&>(int&&, int&) | D::D(A&&, A&&)
=================================+============================
7: Construct D with (rvalue, const lvalue)...
---------------------------------+----------------------------
A::A(int) | A::A(int)
A::A(A&&) | A::A(A&&)
A::A(const A&) | A::A(const A&)
D::D<A, const A&>(A&&, const A&) | D::D(A&&, const A&)
=================================+============================
...etc | ...etc
---------------------------------+----------------------------

As I read this it appears that the parameterized constructor version is
*as* efficient in this sense as the overloaded-constructor version.
What is more, in the case where the parameters are lvalue or rvalue
ints, two moves are avoided in all cases.

The main difference, however, is that non-const lvalues are passed into
the constructor by non-const lvalue refs for the parameterized
constructor version (see 1., 2. & 3, above). And, there is a further
significant difference. If you modify A's constructor so that it is
explicit then the code fails for the overloaded-constructor version when
invoked with lvalue or rvalue integers. However, for the parameterized
constructor version, it *succeeds*, instantiating the constructor with a
combination of int/int& template arguments (see 6. & 7., above). This
may, or may not, be a boon or a bane, depending upon what is required by
the model as a whole.

Note: the above was compiled with gcc-4.5.0, which correctly implements
the change from March 2009 (so Pete informs us) preventing rvalue
references binding to lvalue refs.

Regards

Paul Bibbings
 
S

SG

I've had another look at this and wonder if the following example might
offer something useful.

Well, you posted something that has basically been called "perfect
forwarding" before in this thread. But you included a type (A) that
offers an implicit conversion from int which is an interesting test
case as far as I'm concerned ...

[snip]
   class A
   {
   public:
      A(int i) { std::cout << "A::A(int)\n"; }          // ctor
      A(const A&) { std::cout << "A::A(const A&)\n"; }  // copy ctor
      A(A&&) { std::cout << "A::A(A&&)\n"; }            // move ctor
   };
[snip]

   int main()
   {
      std::cout << "0: Initialize lvalues (A)...\n";
      A a1(1), a2(2);
      const A a3(3);
[snip]

      std::cout << "\n5: Construct D with (rvalue, rvalue)!...\n";
      D d5(1, 2);

      std::cout << "\n6: Construct D with (rvalue, lvalue)...\n";
      int i2 = 2;
      D d6(1, i2);
[snip]

=================================+============================
template ctor | overloaded ctor
[snip]

   =================================+============================
   5: Construct D with (rvalue, rvalue)!...
   ---------------------------------+----------------------------
   A::A(int)                        | A::A(int)
   A::A(int)                        | A::A(int)
                                    | A::A(A&&)
                                    | A::A(A&&)
   D::D<int, int>(int&&, int&&)     | D::D(A&&, A&&)
   =================================+============================
   6: Construct D with (rvalue, lvalue)...
   ---------------------------------+----------------------------
   A::A(int)                        | A::A(int)
   A::A(int)                        | A::A(int)
                                    | A::A(A&&)
                                    | A::A(A&&)
   D::D<int, int&>(int&&, int&)     | D::D(A&&, A&&)

Even though in test case 6 the 2nd parameter is an lvalue of type int
the overload D::D(A&&,A&&) is selected. So, GCC 4.5 creates a
temporary A object from the int lvalue and binds it to a reference of
type A&&. This is the behavior you might expect. I certainly did.
Unfortunately, the current draft actually forbids this, too. I already
started a comp.std.c++ thread on this:

http://groups.google.com/group/comp.std.c++/browse_thread/thread/2b72ea155e039ac6

According to the current draft:

int x = 3;
A&& foo = x; // ill-formed!
A&& foo = A(x); // OK

Even though the conversion from int to A is implicit, the initializer
is still an lvalue expression and no rule in §8.5.3 of the current
draft allows an rvalue reference to be initialized with an lvalue
expression. So, a compiler conforming to the current draft should have
selected the overload

D::D(A&&, const A&);

in the test case 6. I really hope this will be changed. IMHO it makes
little sense to forbit the temporary to bind to the reference of type
A&& just because the argument was an lvalue expression. So, the const
lvalue reference is initialized with a temporary which kind of works
against the original intent of rvalue references.
As I read this it appears that the parameterized constructor version is
*as* efficient in this sense as the overloaded-constructor version.

The perfect forwarding approach is even better under the current rules
in terms of efficiency (judging by the kind and number of constructors
that are called).

[snip]
The main difference, however, is that non-const lvalues are passed into
the constructor by non-const lvalue refs for the parameterized
constructor version (see 1., 2. & 3, above).

Right. In the general perfect-forwarding case this is not an issue.
And here, it isn't an issue either because we know that string doesn't
behave like auto_ptr. :)
And, there is a further
significant difference. If you modify A's constructor so that it is
explicit then the code fails for the overloaded-constructor version when
invoked with lvalue or rvalue integers. However, for the parameterized
constructor version, it *succeeds*, instantiating the constructor with a
combination of int/int& template arguments (see 6. & 7., above). This
may, or may not, be a boon or a bane, depending upon what is required by
the model as a whole.

You can constrain the template like I did to only accept types that
are implicitly convertible to A. This has other advantages, too.

Cheers!
SG
 
P

Paul Bibbings

SG said:
Well, you posted something that has basically been called "perfect
forwarding" before in this thread.

I've gone back to have a look through the thread and found your post in
which you had used perfect forwarding. I had bookmarked this for future
study and so hadn't picked up that you'd already posted on this. One
thing I'd like to ask, though... In your example you make use of the
setter void setter_III(...) whereas in my example I have parameterized
the constructor directly. I was wondering what the comparitive
advantages/disadvantages might be between the two approaches.
But you included a type (A) that
offers an implicit conversion from int which is an interesting test
case as far as I'm concerned ...
Even though in test case 6 the 2nd parameter is an lvalue of type int
the overload D::D(A&&,A&&) is selected. So, GCC 4.5 creates a
temporary A object from the int lvalue and binds it to a reference of
type A&&. This is the behavior you might expect. I certainly did.
Unfortunately, the current draft actually forbids this, too. I already
started a comp.std.c++ thread on this:

http://groups.google.com/group/comp.std.c++/browse_thread/thread/2b72ea155e039ac6

According to the current draft:

int x = 3;
A&& foo = x; // ill-formed!
A&& foo = A(x); // OK

Even though the conversion from int to A is implicit, the initializer
is still an lvalue expression and no rule in §8.5.3 of the current
draft allows an rvalue reference to be initialized with an lvalue
expression. So, a compiler conforming to the current draft should have
selected the overload

D::D(A&&, const A&);

in the test case 6. I really hope this will be changed. IMHO it makes
little sense to forbit the temporary to bind to the reference of type
A&& just because the argument was an lvalue expression. So, the const
lvalue reference is initialized with a temporary which kind of works
against the original intent of rvalue references.

This is interesting, and I hadn't picked up on it in relation to my
analysis of the example that I posted. I had, however, seen your thread
on comp.std.c++ and will follow it with interest. I see that thread is
still, in part, waiting on Daniel's "I need more time to think about
this." My impression is that Daniel - whose position as a member of the
committee is well known - is taking this very seriously. What do you
think might be the chances of a change in the direction that you
advocate prior to ratification? Ordinarily I might have thought that
such a significant change would be beyond what is permitted at this
point in time.

You can constrain the template like I did to only accept types that
are implicitly convertible to A. This has other advantages, too.

I like this. As I recall there was a post in one of the C++ forums
attributing the motif to Howard Hinnant, who swiftly corrected the
/mis/attribution which, IIRC, properly belongs with Beman Dawes. It is
precisely what I was looking for for my own example but, again, having
not studied your earlier example closely prior to posting, I was not
reminded of it until I came back to read what you had already offered.

Regards

Paul Bibbings
 
S

Sousuke

I'm trying to create a Forwarded template class to do something
similar to perfect forwarding as presented by SG, but without having
to templatize the constructor. The ThreeStrings class should look like
this:

class ThreeStrings
{
public:
ThreeStrings(
Forwarded<std::string> s1,
Forwarded<std::string> s2,
Forwarded<std::string> s3
) : m_s1(s1), m_s2(s2), m_s3(s3)
{
}

private:
std::string m_s1;
std::string m_s2;
std::string m_s3;
};

I've tried several permutations of the following:

template<class T>
class Forwarded
{
private:
typedef decltype(std::forward<T>(*static_cast<T*>(nullptr)))
ForwardedType;
ForwardedType m_forwarded;

public:
template<class T2>
Forwarded(T2&& forwarded) :
m_forwarded(std::forward<T>(forwarded))
{
}

operator ForwardedType() const
{
return std::forward<T>(m_forwarded);
}
};

However, when testing it with a class like ThreeStrings, it always
invokes the std::string copy constructor instead of the move
constructor, even when passing a std::string temporary.

Hope somebody can figure out a way to make it work, as it would be the
perfect way to deal with the overloads avalanche problem!
 
P

Paul Bibbings

Sousuke said:
I'm trying to create a Forwarded template class to do something
similar to perfect forwarding as presented by SG, but without having
to templatize the constructor. The ThreeStrings class should look like
this:

class ThreeStrings
{
public:
ThreeStrings(
Forwarded<std::string> s1,
Forwarded<std::string> s2,
Forwarded<std::string> s3
) : m_s1(s1), m_s2(s2), m_s3(s3)
{
}

private:
std::string m_s1;
std::string m_s2;
std::string m_s3;
};

I've tried several permutations of the following:

template<class T>
class Forwarded
{
private:
typedef decltype(std::forward<T>(*static_cast<T*>(nullptr)))
ForwardedType;
ForwardedType m_forwarded;

public:
template<class T2>
Forwarded(T2&& forwarded) :
m_forwarded(std::forward<T>(forwarded))
{
}

operator ForwardedType() const
{
return std::forward<T>(m_forwarded);
}
};

However, when testing it with a class like ThreeStrings, it always
invokes the std::string copy constructor instead of the move
constructor, even when passing a std::string temporary.

Here's what I get under gcc-4.5.0 (I don't think I've picked up what
compiler you are using - VS2010, since you have nullptr?)

13:30:52 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP $cat forwarded_test.cpp
// file: forwarded_test.cpp

#include <iostream>
#include <utility>

#define nullptr 0

template<class T>
class Forwarded
{
private:
typedef decltype(std::forward<T>(*static_cast<T*>(nullptr)))
ForwardedType;
ForwardedType m_forwarded;
public:
template<class T2>
Forwarded(T2&& forwarded)
: m_forwarded(std::forward<T>(forwarded))
{ }
operator ForwardedType() const
{ return std::forward<T>(m_forwarded); }
};

struct B
{
explicit B(int)
{ std::cout << "B::B()\n"; }
B(const B&)
{ std::cout << "B::B(const B&)\n"; }
B(B&&)
{ std::cout << "B::B(B&&)\n"; }
};

class ThreeBs
{
public:
ThreeBs(Forwarded<B> b1, Forwarded<B> b2, Forwarded<B> b3)
: m_b1(b1), m_b2(b2), m_b3(b3)
{ }
private:
B m_b1;
B m_b2;
B m_b3;
};

int main()
{
B b1(1), b3(3);
ThreeBs threeBs(b1, B(2), b3);
}

13:30:58 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP $i686-pc-cygwin-g++-4.5.0 -std=c++0x
-O0 -static -o forwarded_test forwarded_test.cpp

13:31:11 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP $./forwarded_test
B::B()
B::B()
B::B()
B::B(B&&)
B::B(B&&)
B::B(B&&)

The copy constructor for B isn't used at all, evidently:

13:34:35 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP $nm --demangle --defined-only
forwarded_test.exe | grep B\(
0040c980 T B::B(B&&)
0040c99c T B::B(int)

Also, a constructor for both the lvalue and rvalue reference arguments
are instantiated and ForwardedType evaluates to B&&.

13:34:54 Paul Bibbings@JIJOU
/cygdrive/d/CPPProjects/CLCPP $nm --demangle --defined-only
forwarded_test.exe | grep Forwarded
// ...
0040c9b8 T ThreeBs::ThreeBs(Forwarded<B>, Forwarded<B>, Forwarded<B>)
0040ca18 T Forwarded<B>::Forwarded<B&>(B&&&)
0040ca30 T Forwarded<B>::Forwarded<B>(B&&)
0040eed0 T Forwarded<B>::eek:perator B&&() const

This equates to the following (effective) instantiation of B:

template<>
class Forwarded<B>
{
private:
B&& m_forwarded;
public:
template<>
Forwarded<B&>(B& forwarded)
: m_forwarded(std::forward<B>(forwarded))
{ }
template<>
Forwarded<B>(B&& forwarded)
: m_forwarded(std::forward<B>(forwarded))
{ }
operator B&&() const
{
return std::forward<B>(m_forwarded);
}
};

and, what is more, this /probably/ shouldn't work according to the
letter of the Standard, since for the lvalue reference constructor the
the result of std::forward<B>(forwarded) - an lvalue reference -
probably shouldn't bind to the rvalue reference m_forwarded [citation
needed :]

I'm getting a little lost in this, I have to say, but I wonder if your
problem (which appears to be slightly different from my problem here) is
that the type of Forwarded<T>::m_forwarded is dependent on T and is the
same for all objects of type Forwarded<T> for any T - T&&, in the case
of the above code compiled with gcc-4.5.0 - whereas the parameter of the
constructor may be either an lvalue or rvalue reference. I think that
this *fixes* the type of `copy' that is applied - move, in my case; you
say you're getting copy always.

Apologies if this is more confusing than helpful.

What's the relevance - or rather, what is the expected effect - of the
idiom:

typedef decltype(std::forward<T>(*static_cast<T*>(nullptr)))
ForwardedType;

I've not followed this through against the Standard, but the result of
static_cast<T*>(nullptr) is an rvalue, surely, but I can't guess what
that necessarily means for the result of *static_cast<T*>(nullptr).
What is more, T cannot be a reference type - i.e., you couldn't form
ForwardedType<std::string&> because that would attempt to form a pointer
to a reference. Same with ForwardedType<std::string&&>, I guess.

Regards

Paul Bibbings
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top