Perfect Forwarding + static_assert [C++0x]

  • Follow


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).
0
Reply Scott 12/4/2010 6:07:14 AM

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.
0
Reply Marc 12/4/2010 11:44:11 AM


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

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? =A0I 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_ =3D 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 =3D 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 =3D *p;
      else   target =3D move(*q);
    }
  private:
    T const* p;
    T * q;
  };

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


Cheers!
Sebastian
0
Reply SG 12/4/2010 12:56:28 PM

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

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

Cheers!
SG
0
Reply SG 12/4/2010 1:23:40 PM

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).
0
Reply Scott 12/4/2010 6:36:22 PM

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!
0
Reply Johannes 12/5/2010 5:12:25 AM

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).
0
Reply Scott 12/5/2010 8:21:01 PM

6 Replies
324 Views

(page loaded in 0.09 seconds)

Similiar Articles:











7/23/2012 12:29:05 PM


Reply: