Verbosity when optimizing with rvalue references

Discussion in 'C++' started by Sousuke, Jun 24, 2010.

  1. Sousuke

    Sousuke Guest

    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?
     
    Sousuke, Jun 24, 2010
    #1
    1. Advertising

  2. * 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


    --
    blog at <url: http://alfps.wordpress.com>
     
    Alf P. Steinbach /Usenet, Jun 24, 2010
    #2
    1. Advertising

  3. Sousuke

    Sousuke Guest

    On Jun 24, 10:28 am, "Leigh Johnston" <> wrote:
    > "Sousuke" <> wrote in message
    >
    > news:...
    >
    >
    >
    >
    >
    > > 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?

    >
    > 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!
     
    Sousuke, Jun 24, 2010
    #3
  4. Sousuke

    Bo Persson Guest

    Sousuke wrote:
    > On Jun 24, 10:28 am, "Leigh Johnston" <> wrote:
    >> "Sousuke" <> wrote in message
    >>

    [...]
    >>
    >>> 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
     
    Bo Persson, Jun 24, 2010
    #4
  5. Sousuke

    Sousuke Guest

    On Jun 24, 11:11 am, "Alf P. Steinbach /Usenet" <alf.p.steinbach
    > wrote:
    > * 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?


    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 :)
     
    Sousuke, Jun 24, 2010
    #5
  6. Sousuke

    Bo Persson Guest

    Alf P. Steinbach /Usenet wrote:
    > * 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?
    >


    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
     
    Bo Persson, Jun 24, 2010
    #6
  7. Sousuke

    SG Guest

    On 24 Jun., 17:23, Sousuke wrote:
    > 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
     
    SG, Jun 24, 2010
    #7
  8. Sousuke <> wrote:
    > 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.)
     
    Juha Nieminen, Jun 24, 2010
    #8
  9. "Alf P. Steinbach /Usenet" <> writes:

    > * 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?


    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
     
    Paul Bibbings, Jun 24, 2010
    #9
  10. "Alf P. Steinbach /Usenet" <> writes:

    > * 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?


    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
     
    Paul Bibbings, Jun 24, 2010
    #10
  11. Sousuke

    Sousuke Guest

    On Jun 24, 12:18 pm, SG <> wrote:
    > On 24 Jun., 17:23, Sousuke wrote:
    >
    >
    >
    >
    >
    > > 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.
     
    Sousuke, Jun 24, 2010
    #11
  12. Sousuke <> writes:

    > On Jun 24, 11:11 am, "Alf P. Steinbach /Usenet" <alf.p.steinbach
    > > wrote:
    >> * 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?

    >
    > 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
     
    Paul Bibbings, Jun 24, 2010
    #12
  13. "Bo Persson" <> writes:

    > Alf P. Steinbach /Usenet wrote:
    >> * 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?
    >>

    >
    > 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
     
    Paul Bibbings, Jun 24, 2010
    #13
  14. Paul Bibbings <> writes:

    > "Bo Persson" <> writes:


    >> 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);
    > }


    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
     
    Paul Bibbings, Jun 24, 2010
    #14
  15. Paul Bibbings <> writes:

    > Sousuke <> writes:
    >
    >> On Jun 24, 11:11 am, "Alf P. Steinbach /Usenet" <alf.p.steinbach
    >> > wrote:
    >>> * 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?

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


    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
     
    Paul Bibbings, Jun 24, 2010
    #15
  16. Sousuke <> writes:

    > 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
     
    Paul Bibbings, Jun 25, 2010
    #16
  17. Sousuke

    SG Guest

    On 25 Jun., 15:18, Paul Bibbings wrote:
    >
    > 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
     
    SG, Jun 25, 2010
    #17
  18. SG <> writes:

    > On 25 Jun., 15:18, Paul Bibbings wrote:
    >>
    >> 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.


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

    <snip detail />
    >
    > 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.

    <snip>

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


    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
     
    Paul Bibbings, Jun 25, 2010
    #18
  19. Sousuke

    Sousuke Guest

    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!
     
    Sousuke, Jun 26, 2010
    #19
  20. Sousuke <> writes:

    > 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
     
    Paul Bibbings, Jun 27, 2010
    #20
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Kavya
    Replies:
    9
    Views:
    515
    Dik T. Winter
    Oct 28, 2006
  2. Chris Fairles
    Replies:
    1
    Views:
    374
    Howard Hinnant
    Sep 4, 2007
  3. Cory Nelson

    rvalue references

    Cory Nelson, Mar 19, 2008, in forum: C++
    Replies:
    3
    Views:
    290
    Alf P. Steinbach
    Mar 20, 2008
  4. Juha Nieminen
    Replies:
    13
    Views:
    625
    Edek Pienkowski
    Aug 29, 2012
  5. K. Frank
    Replies:
    4
    Views:
    182
Loading...

Share This Page