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

Perfect Forwarding + static_assert [C++0x]

13 views
Skip to first unread message

Scott Meyers

unread,
Dec 4, 2010, 1:07:14 AM12/4/10
to
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).

Marc

unread,
Dec 4, 2010, 6:44:11 AM12/4/10
to
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.

SG

unread,
Dec 4, 2010, 7:56:28 AM12/4/10
to
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

unread,
Dec 4, 2010, 8:23:40 AM12/4/10
to
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

Scott Meyers

unread,
Dec 4, 2010, 1:36:22 PM12/4/10
to
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.

Johannes Schaub (litb)

unread,
Dec 5, 2010, 12:12:47 AM12/5/10
to
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!

Scott Meyers

unread,
Dec 5, 2010, 3:21:01 PM12/5/10
to
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 .

0 new messages