Perfect Forwarding + static_assert [C++0x]

S

Scott Meyers

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 said:
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
 
M

Marc

Scott said:
template<typename T>
void setName(T&& newName)
{
static_assert(std::is_same said:
::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.
 
S

SG

[...]
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 said:
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
 
S

SG

[...]

  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
 
S

Scott Meyers

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
 
J

Johannes Schaub (litb)

Scott said:
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 said:
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!
 
S

Scott Meyers

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
 

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

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top