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).
> 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.
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
But apssing string literals won't work anymore because two user-
defined conversions would be involved.
Cheers!
SG
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.
> 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!
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 .