Perfect Forwarding + static_assert [C++0x]

Discussion in 'C++' started by Scott Meyers, Dec 4, 2010.

  1. Scott Meyers

    Scott Meyers Guest

    Suppose I have a setter function, and I'd like it to forward its argument as an
    lvalue or an rvalue to whatever constructor will be used to do the setting. I
    can overload the setter like this:

    class Widget {
    public:
    ...
    void setName(const std::string& newName) // set from lvalue
    { name = newName; }

    void setName(std::string&& newName) // set from rvalue
    { name = std::move(newName); }

    ...
    private:
    std::string name;
    };

    I can also use a template member function and perfect forwarding:

    class Widget {
    public:
    ...
    template<typename T>
    void setName(T&& newName)
    { name = std::forward<T>(newName); }
    ...
    };

    The template will forward any type that can be used to initialize the string,
    i.e., it will accept types other than std::string. Suppose, for whatever wacky
    reason, I really want to forward only a std::string. I came up with this:

    template<typename T>
    void setName(T&& newName)
    {
    static_assert(std::is_same<std::remove_cv<std::remove_reference<T>::type
    >::type,

    std::string
    >::value,

    "T must be a [const] std::string“
    );

    name = std::forward<T>(newName);
    };

    VC10 swallows it and seems to behave the way I want. gcc 4.5 doesn't compile
    it. Questions:

    1. Is there some reason the above should not compile?
    2. Assuming I want to do what I say I want to do, is there a better way to do
    it? I assume I could also play games with enable_if, but I think the
    incantation would be no simpler than the static_assert.

    Thanks,

    Scott

    --
    * C++ and Beyond Encore!: Meyers, Sutter, & Alexandrescu, Dec. 13-16 near
    Seattle (http://cppandbeyond.com/)
    * License my training materials for commercial (http://tinyurl.com/yfzvkp9) or
    personal use (http://tinyurl.com/yl5ka5p).
    Scott Meyers, Dec 4, 2010
    #1
    1. Advertising

  2. Scott Meyers

    Marc Guest

    Scott Meyers wrote:

    > template<typename T>
    > void setName(T&& newName)
    > {
    > static_assert(std::is_same<std::remove_cv<std::remove_reference<T>::type
    > >::type,

    > std::string
    > >::value,

    > "T must be a [const] std::string“
    > );
    >
    > name = std::forward<T>(newName);
    > };
    >
    > VC10 swallows it and seems to behave the way I want. gcc 4.5 doesn't compile
    > it. Questions:
    >
    > 1. Is there some reason the above should not compile?


    Missing "typename"?

    > 2. Assuming I want to do what I say I want to do, is there a better way to do
    > it? I assume I could also play games with enable_if, but I think the
    > incantation would be no simpler than the static_assert.


    The incantation would be roughly the same, but SFINAE would apply.
    Marc, Dec 4, 2010
    #2
    1. Advertising

  3. Scott Meyers

    SG Guest

    On 4 Dez., 07:07, Scott Meyers wrote:
    > [...]
    > I really want to forward only a std::string. I came up with this:
    >
    >   template<typename T>
    >   void setName(T&& newName)
    >   {
    > static_assert(
    > std::is_same<
    > std::remove_cv<
    > std::remove_reference<T>::type
    >            >::type,
    >            std::string
    >         >::value, "T must be a [const] std::string
    >      );
    >
    >      name = std::forward<T>(newName);
    >    };


    As Marc said already, a "typename" appears to be missing. In addition,
    I'll like to mention that std::decay typically works as a shortcut for
    remove_cv<remove_refernence<...>>

    > VC10 swallows it and seems to behave the way I want.
    > gcc 4.5 doesn't compile


    I guess that's because VC10 doesn't do a proper two-phase lookup.

    > 2. Assuming I want to do what I say I want to do, is there a
    > better way to do it?  I assume I could also play games with
    > enable_if, but I think the incantation would be no simpler
    > than the static_assert.


    I was just about to suggest enable_if here. That's seems (at least for
    function templates) like a good way to constrain them in order to
    reduce the size of the overload resolution set. With a failing
    enable_if a function doesn't make it into the overload resolution set
    while a static_assert would only be checked after overload resolution.
    Instead of restricting the parameter to std::string (or references to
    string), you should consider conversion, so that you can also pass
    string literals:

    template<class T>
    enable_if< is_convertible<T,string>::value,
    void>::type setName(T&& newName)
    {
    name_ = forward<T>(newName);
    }

    To hide the template stuff one could use a wrapper that remembers the
    address of the argument object and its value category so it can later
    perform the corresponding assignment:

    template<class T>
    class epa // ep = efficient passing / assignment
    {
    public:
    epa(T const& x) : p(&x), q(0) {}
    epa(T && x) : p(0), q(&x) {}
    void assign_to(T & target) {
    if (p) target = *p;
    else target = move(*q);
    }
    private:
    T const* p;
    T * q;
    };

    void YourClass:setName(epa<string> newName)
    {
    newName.assign_to(this->name);
    }


    Cheers!
    Sebastian
    SG, Dec 4, 2010
    #3
  4. Scott Meyers

    SG Guest

    On 4 Dez., 13:56, SG wrote:
    > [...]
    >
    >   void YourClass:setName(epa<string> newName)
    >   {
    >       newName.assign_to(this->name);
    >   }


    But apssing string literals won't work anymore because two user-
    defined conversions would be involved.

    Cheers!
    SG
    SG, Dec 4, 2010
    #4
  5. Scott Meyers

    Scott Meyers Guest

    On 12/4/2010 4:56 AM, SG wrote:
    > As Marc said already, a "typename" appears to be missing. In addition,
    > I'll like to mention that std::decay typically works as a shortcut for
    > remove_cv<remove_refernence<...>>


    Nice catch to you both on the "typename" issue (duh), and thanks for the pointer
    to std::decay, which I did not know about.

    > Instead of restricting the parameter to std::string (or references to
    > string), you should consider conversion, so that you can also pass
    > string literals:
    >
    > template<class T>
    > enable_if< is_convertible<T,string>::value,
    > void>::type setName(T&& newName)
    > {
    > name_ = forward<T>(newName);
    > }


    But if I wanted to allow conversions, I could just skip the static_assert (or
    enable_if), because the original code will take anything and forward it to a
    std::string constructor. That code will compile only if the type passed is
    convertible to a std::string.

    Scott

    --
    * C++ and Beyond Encore!: Meyers, Sutter, & Alexandrescu, Dec. 13-16 near
    Seattle (http://cppandbeyond.com/)
    * License my training materials for commercial (http://tinyurl.com/yfzvkp9) or
    personal use (http://tinyurl.com/yl5ka5p).
    Scott Meyers, Dec 4, 2010
    #5
  6. Scott Meyers wrote:

    > Suppose I have a setter function, and I'd like it to forward its argument
    > as an
    > lvalue or an rvalue to whatever constructor will be used to do the
    > setting. I can overload the setter like this:
    >
    > class Widget {
    > public:
    > ...
    > void setName(const std::string& newName) // set from lvalue
    > { name = newName; }
    >
    > void setName(std::string&& newName) // set from rvalue
    > { name = std::move(newName); }
    >
    > ...
    > private:
    > std::string name;
    > };
    >
    > I can also use a template member function and perfect forwarding:
    >
    > class Widget {
    > public:
    > ...
    > template<typename T>
    > void setName(T&& newName)
    > { name = std::forward<T>(newName); }
    > ...
    > };
    >


    Notice that the first code is superior IMO because it allows the caller to
    choose between list initialization and non-list initialization.

    > The template will forward any type that can be used to initialize the
    > string,
    > i.e., it will accept types other than std::string. Suppose, for whatever
    > wacky
    > reason, I really want to forward only a std::string. I came up with this:
    >
    > template<typename T>
    > void setName(T&& newName)
    > {
    >

    static_assert(std::is_same<std::remove_cv<std::remove_reference<T>::type
    > >::type,

    > std::string
    > >::value,

    > "T must be a [const] std::string“
    > );
    >
    > name = std::forward<T>(newName);
    > };
    >
    > VC10 swallows it and seems to behave the way I want. gcc 4.5 doesn't
    > compile
    > it. Questions:
    >
    > 1. Is there some reason the above should not compile?


    I wrote a FAQ about when to place typename and template:
    http://stackoverflow.com/questions/610245/where-to-put-the-template-and-
    typename-on-dependent-names/613132#613132 . I would be glad to hear about
    your feedback!
    Johannes Schaub (litb), Dec 5, 2010
    #6
  7. Scott Meyers

    Scott Meyers Guest

    On 12/4/2010 9:12 PM, Johannes Schaub (litb) wrote:
    > Notice that the first code is superior IMO because it allows the caller to
    > choose between list initialization and non-list initialization.


    Ah, an infamous case of imperfect forwarding, thanks for reminding me. 0 as a
    null pointer can't be perfect-forwarded, either. There are a few other places
    where perfect forwarding demonstrates its imperfections. Details are in the
    discussion thread at http://tinyurl.com/26wz3f7 .

    Scott

    --
    * C++ and Beyond Encore!: Meyers, Sutter, & Alexandrescu, Dec. 13-16 near
    Seattle (http://cppandbeyond.com/)
    * License my training materials for commercial (http://tinyurl.com/yfzvkp9) or
    personal use (http://tinyurl.com/yl5ka5p).
    Scott Meyers, Dec 5, 2010
    #7
    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. Scott Meyers
    Replies:
    20
    Views:
    1,095
    itaj sherman
    Mar 16, 2011
  2. Andrew Tomazos
    Replies:
    1
    Views:
    983
    Andrew Tomazos
    Dec 23, 2011
  3. Alexis Nikichine

    Perfect function forwarding

    Alexis Nikichine, Dec 27, 2005, in forum: Javascript
    Replies:
    6
    Views:
    157
    Alexis Nikichine
    Dec 28, 2005
  4. dervih
    Replies:
    3
    Views:
    503
    dervih
    Jul 13, 2012
  5. Replies:
    3
    Views:
    338
Loading...

Share This Page