Specializing Perfect Forwarding Templates?

S

Scott Meyers

Function templates can be partially or fully specialized, but is it
possible to specialize templates that perform perfect forwarding? My
sense is that it's not, because the special type deduction rule that
makes perfect forwarding work (the one that distinguishes lvalue and
rvalue arguments for function templates with a declared parameter type
of T&& -- 14.8.2.1/3 in the most recent draft C++0x) seems to apply only
to general templates. But maybe I'm overlooking something.

If I give the program below to VC10 and gcc 4.5, they behave as the
comments indicate. My attempt to partially specialize a perfect
forwarding template for pointer types fails. The code compiles and
runs, but the partial specialization is invoked only for rvalue pointer
types, never for lvalue pointer types.

I have two questions:
1. Am I correct that perfect forwarding templates cannot be specialized?
2. Does it ever make sense to want to?

Thanks,

Scott



#include <iostream>
#include <utility>

void f(std::string*&)
{
std::cout << "f(lref)\n";
}

void f(const std::string*&)
{
std::cout << "f(const lref)\n";
}

void f(std::string*&&)
{
std::cout << "f(rref)\n";
}

template<typename T>
void fwd(T&& param)
{
std::cout << "General forwarding template => ";
f(std::forward<T>(param));
}

template<typename T>
void fwd(T*&& param)
{
std::cout << "T*&& forwarding template => ";
f(std::forward<T*>(param));
}

int main()
{
std::string* ps;
const std::string *pcs;

fwd(ps); // calls general template
fwd(pcs); // calls general template
fwd((std::string*&&)ps); // calls specialized template
}
 
A

Alf P. Steinbach /Usenet

* Scott Meyers, on 16.03.2011 06:22:
Function templates can be partially or fully specialized,

Sorry, but at which version have partial specialization of function templates
been introduced, and can you give reference?

AFAIK what looks like a partial specialization is still just overloading.

but is it possible to
specialize templates that perform perfect forwarding? My sense is that it's not,
because the special type deduction rule that makes perfect forwarding work (the
one that distinguishes lvalue and rvalue arguments for function templates with a
declared parameter type of T&& -- 14.8.2.1/3 in the most recent draft C++0x)
seems to apply only to general templates. But maybe I'm overlooking something.

If I give the program below to VC10 and gcc 4.5, they behave as the comments
indicate. My attempt to partially specialize a perfect forwarding template for
pointer types fails. The code compiles and runs, but the partial specialization
is invoked only for rvalue pointer types, never for lvalue pointer types.

I have two questions:
1. Am I correct that perfect forwarding templates cannot be specialized?
2. Does it ever make sense to want to?

Thanks,

Scott



#include <iostream>
#include <utility>

void f(std::string*&)
{
std::cout << "f(lref)\n";
}

void f(const std::string*&)
{
std::cout << "f(const lref)\n";
}

void f(std::string*&&)
{
std::cout << "f(rref)\n";
}

template<typename T>
void fwd(T&& param)
{
std::cout << "General forwarding template => ";
f(std::forward<T>(param));
}

template<typename T>
void fwd(T*&& param)
{
std::cout << "T*&& forwarding template => ";
f(std::forward<T*>(param));
}

int main()
{
std::string* ps;
const std::string *pcs;

fwd(ps); // calls general template
fwd(pcs); // calls general template
fwd((std::string*&&)ps); // calls specialized template
}

Well it's late in the day for me, but fiddling a little with your code produced
an apparent difference between the general template and the pointer arg template:


<code>
#include <iostream>
#include <utility>

void f(std::string*&)
{
std::cout << "f(lref)\n";
}

void f(const std::string*&)
{
std::cout << "f(const lref)\n";
}

void f(std::string*&&)
{
std::cout << "f(rref)\n";
}

template<typename T>
void fwd(T&& param)
{
std::cout << "General forwarding template => ";
f(std::forward<T>(param));
}

template<typename T>
void ptrfwd(T*&& param)
{
std::cout << "T*&& forwarding template => ";
f( std::forward<T*>(param));
}

template<typename T>
void fwd(T*&& param)
{ ptrfwd( std::forward<T*>(param) ); }

template<typename T>
void fwd(T*& param)
{ ptrfwd( param ); }

int main()
{
std::string* ps = 0;
const std::string *pcs = 0;

fwd(ps); // calls general template
fwd(pcs); // calls general template
fwd((std::string*&&)ps); // calls specialized template
}
</code>


<compilation>
C:\test> msvc x.cpp
x.cpp
x.cpp(39) : error C2664: 'ptrfwd' : cannot convert parameter 1 from 'std::string
*' to 'std::string *&&'
You cannot bind an lvalue to an rvalue reference
x.cpp(46) : see reference to function template instantiation 'void
fwd<std::string>(T *&)' being compiled
with
[
T=std::string
]
x.cpp(39) : error C2664: 'ptrfwd' : cannot convert parameter 1 from 'const
std::string *' to 'const std::string *&&'
You cannot bind an lvalue to an rvalue reference
x.cpp(47) : see reference to function template instantiation 'void
fwd<const std::string>(T *&)' being compiled
with
[
T=const std::string
]

C:\test> _
</compilation>


Why this result, cannot bind lvalue to rvalue-ref, when 'ptrwfd' is a general
template?


Cheers & hth.,

- Alf
 
A

Alf P. Steinbach /Usenet

* Scott Meyers, on 16.03.2011 06:22:
I have two questions:
1. Am I correct that perfect forwarding templates cannot be specialized?

Code below seems to work OK:


<code>
#include <iostream>
#include <utility>

void f(std::string*&)
{
std::cout << "f(lref)\n";
}

void f(const std::string*&)
{
std::cout << "f(const lref)\n";
}

void f(std::string*&&)
{
std::cout << "f(rref)\n";
}

template<typename T>
void fwd(T&& param)
{
std::cout << "General forwarding template => ";
f(std::forward<T>(param));
}

template<typename T>
void ptrfwd(T&& param)
{
//STATIC_ASSERT( T is pointer type )
std::cout << "T*&& forwarding template => ";
f( std::forward<T>(param));
}

template<typename T>
void fwd(T*&& param)
{ ptrfwd( std::forward<T*>(param) ); }

template<typename T>
void fwd(T*& param)
{ ptrfwd( param ); }

int main()
{
std::string* ps = 0;
const std::string *pcs = 0;

fwd(ps); // calls specialized template
fwd(pcs); // calls specialized template
fwd((std::string*&&)ps); // calls specialized template
}
</code>


Cheers & hth.,

- Alf (hey, why don't you mention me somewhere? I wanna be fammous! lol)
 
S

SG

Function templates can be partially or fully specialized,

Can they be? partially specialized? If so, this would be a new C++0x
feature. I take your word for it.
but is it
possible to specialize templates that perform perfect forwarding?

The code you provided does not contain a partial specialization of a
function template. You *overloaded* the function template fwd with
another "more specialized" one. That's different.
void f(std::string*&)
{
   std::cout << "f(lref)\n";
}

void f(const std::string*&)
{
   std::cout << "f(const lref)\n";
}

void f(std::string*&&)
{
   std::cout << "f(rref)\n";
}

template<typename T>
void fwd(T&& param)
{
   std::cout << "General forwarding template => ";
   f(std::forward<T>(param));
}

template<typename T>
void fwd(T*&& param)
{
   std::cout << "T*&& forwarding template => ";
   f(std::forward<T*>(param));
}

From your use of std::forward I gather that you expect this template
to catch pointer lvalues and rvalues. But the deduction rule that
makes perfect forwarding possible is restricted to a function
parameter pattern "T&&" where T is a template parameter. Perfect
forwarding not only relies on the deduction rules but also on
reference collapsing. So, to make T&& an lvalue reference we just set
T to be an lvalue reference (or let the compiler deduce it to be an
lvalue reference). Reference collapsing makes T&& with T=U& into
T&&=U&. But T*&& can *never* be an lvalue reference regardless of what
T is. T*&& is *always* an rvalue reference.

SG
 
S

Scott Meyers

* Scott Meyers, on 16.03.2011 06:22:

Sorry, but at which version have partial specialization of function
templates been introduced, and can you give reference?

AFAIK what looks like a partial specialization is still just overloading.

You're right, my bad. Please forgive my terminological sin.
Code below seems to work OK:
[...]

template<typename T>
void fwd(T*&& param)
{ ptrfwd( std::forward<T*>(param) ); }

template<typename T>
void fwd(T*& param)
{ ptrfwd( param ); }

But notice how you have to overload to support both lvalues and rvalues.
This doesn't scale with multiple parameters, which is the motivation
for perfect forwarding: to be able to write one template that handles
both. If I could "partially specialize" a forwarding template for
pointer types, I'd be able to write a single template to handle all
pointer types, both lvalues and rvalues.

Scott
 
S

Scott Meyers

Can they be? partially specialized? If so, this would be a new C++0x
feature. I take your word for it.

No, as I pointed out in a response to a different post, I made the
common error of saying "specialization" when I meant overloading. I
hang my head in shame.
From your use of std::forward I gather that you expect this template
to catch pointer lvalues and rvalues. But the deduction rule that
makes perfect forwarding possible is restricted to a function
parameter pattern "T&&" where T is a template parameter.

Which is what I said I thought was the case. I was just wondering if I
had overlooked something somewhere. Even in draft form, it would not be
breaking new ground for the C++ standard to grant permission to do
something in one place that appears to not be permitted based on other
parts of the standard.

Scott
 
S

SG

Which is what I said I thought was the case.  I was just wondering if I
had overlooked something somewhere.  Even in draft form, it would not be
breaking new ground for the C++ standard to grant permission to do
something in one place that appears to not be permitted based on other
parts of the standard.

You make it sound like if it was an artificial restriction that could
be easily overcome. But this is not the case. To get what you want we
would need some kind of constrained template and new overload
resolution rules based on these constraints. Example:

template<class T>
requires T=U* or T=U*& for some type U
void fwd(T&&);

SG
 
S

SG

[...] To get what you want we
would need some kind of constrained template and new overload
resolution rules based on these constraints. Example:

  template<class T>
  requires T=U* or T=U*& for some type U
  void fwd(T&&);

Or an alternative way to do perfect forwarding. You *did* point out
with your example that deduction w.r.t. "special, more restrictive
patters" a la T* is not orthogonal to the feature that allows us to
detect lvalues and rvalues. So, for a proper "orthogonal" approach we
could introduce a new kind of template parameter:

template<class T, qualifier Q>
void fwd(T* Q x);

where Q can be one of the 12=2*2*3 combinations you get by pairing
const/non-const, volatile/non-volatile and [no reference]/&/&&. The
deduction rule for Q would pick an lvalue reference for lvalues and an
rvalue reference for rvalues.

This way, we get rid of the issue you pointed out (w.r.t.
orthogonality), we can get rid of the funny/special deduction rule for
T&& (which is already known to cause trouble (*)) and also get a nice
way of expressing member function types

template<class R, class C, qualifier Q, class... P>
void (R (C::*memfunptr)(P...) Q);

without the need to overload std::bind for 12 different combinations
of qualifiers.

On the other hand, getting rid of the funny/special deduction rule for
the "T&&" pattern would force us to use two template parameters to do
perfect forwarding for just one function argument:

template<class...P, qualifiers...Q>
void outer(P Q... params)
{
inner(std::forward<P Q>(params)...);
}

I'm not sure about other implications, but it seems to be worth
thinking about it.

Cheers!
SG
 
I

itaj sherman

Function templates can be partially or fully specialized, but is it
possible to specialize templates that perform perfect forwarding? My
sense is that it's not, because the special type deduction rule that
makes perfect forwarding work (the one that distinguishes lvalue and
rvalue arguments for function templates with a declared parameter type
of T&& -- 14.8.2.1/3 in the most recent draft C++0x) seems to apply only
to general templates. But maybe I'm overlooking something.

What is supposed to happen if you wrap it into template class
specializations?

template< typename T >
class fwd_internal
{
public:
template<typename U>
static void call(U&& param)
{
std::cout << "General forwarding template => ";
f(std::forward<U>(param));
}
}

template< typename T* >
class fwd_internal
{
public:
template<typename U>
static void call(U&& param)
{
std::cout << "T*&& forwarding template => ";
f(std::forward<U>(param));
}
}

template<typename T>
void fwd(T&& param)
{
return fwd_internal<T>::call(std::forward<T>(param));
}

itaj
 
C

crea

Scott Meyers wrote:

Are you the Scott Meyers who wrotes foreword to book "Modern C++ Design" ?
 
S

SG

template< typename T >
class fwd_internal< T* >

In addition, you have to replace

fwd_internal<T>::call(etc);

with

typedef typename std::decay<T>::type Td;
fwd_internal<Td>::call(etc);

to get rid of cv qualifiers and the case when T is deduced to to be an
lvalue reference due to the "funny/special" deduction rule that allows
perfect forwarding.

Cheers!
SG
 
N

Noah Roberts

template<typename T>
void fwd(T*&& param)
{
std::cout << "T*&& forwarding template => ";
f(std::forward<T*>(param));
}

Couple issues here. T can never cause reference "decay". If T is an
lvalue reference then you have the strange parameter type "T&*&&". If T
is an rvalue reference then you have the strange parameter type
"T&&*&&". To implement a perfect forwarding function you need to be
relying on the fact that & && is & and && && is && so that when T is an
lvalue reference, the parameter to the function is, and visa-versa.

Second issue is that std::forward works similarly. It's simply a
template function that takes a T&& and returns T.

Since the parameter for your function is always going to be an rvalue
reference you can expect that lvalue references can't be passed into it
without calling std::move.

You can do what you're trying to do with a dispatching pattern:

#include <iostream>
#include <type_traits>

template < bool Match >
struct function_impl
{
template < typename T >
static void apply(T&& t)
{
std::cout << "Generic version called.\n";
}
};

template < >
struct function_impl<true>
{
template < typename T >
static void apply(T&& t)
{
std::cout << "Specific version called.\n";
}
};

template < typename T >
void f(T&& t)
{
function_impl< std::is_pointer< typename
std::remove_reference<T>::type >::value >::apply(std::forward<T>(t));
}

int * get_ptr() { return nullptr; }
int get_value() { return 0; }

int main()
{
int val = get_value();
int * ptr = get_ptr();

f(val);
f(ptr);
f(get_value());
f(get_ptr());
}

Specific needs may of course mean a more complicated construct.
 
S

Scott Meyers

You can do what you're trying to do with a dispatching pattern:

Simple testing suggests I can achieve the same end with less work and
using an arguably clearer approach via enable_if. Overloading my
forwarding template as follows works for me with VC10 and gcc 4.5 (on
simple tests):

template<typename T>
typename std::enable_if<
!std::is_pointer<
typename std::remove_reference said:
::value
::type
fwd(T&& param)
{
std::cout << "General forwarding template => ";
f(std::forward<T>(param));
}

template<typename T>
typename std::enable_if<
std::is_pointer<
typename std::remove_reference said:
::value
::type
fwd(T&& param)
{
std::cout << "Pointer forwarding template => ";
f(std::forward<T>(param));
}

Am I overlooking a drawback to this technique?

While playing around with this, I was surprised to find that there is no
disable_if in C++0x. Yes, there's no need for it (as shown above), but
it has an established usage history (e.g., in Boost). Does anybody
happen to know why it's not in C++0x?

Thanks,

Scott
 
N

Noah Roberts

Simple testing suggests I can achieve the same end with less work and
using an arguably clearer approach via enable_if.

Entirely possible as you show. Arguably cleaner but I tend to disagree.
I've found that although enable_if works reasonably well when you're
trying to match specific concepts and/or types, when you begin having to
exclude these concepts in the other overloads/specializations to resolve
ambiguity the scaling factor explodes and it tends, in my opinion, to be
less legible than dispatching versions.

If, for example, you needed to overload both versions for some specific
concept, a combination of dispatching at the is_pointer level and either
enable_if or a different dispatch for the function call would probably
end up in slightly more legible code. It would also tend to create more
reuse because the is_pointer bit is already resolved by the general layer.
Am I overlooking a drawback to this technique?

Scalability would be my primary technical concern. Preference would
guide me to prefer the dispatch version otherwise, though I'm sure I
could be convinced otherwise by a good reason to prefer the enable_if
version.
While playing around with this, I was surprised to find that there is no
disable_if in C++0x. Yes, there's no need for it (as shown above), but
it has an established usage history (e.g., in Boost). Does anybody
happen to know why it's not in C++0x?

I wouldn't really know. Perhaps it was just one less thing to argue
about :p
 
M

Marc

Scott said:
Simple testing suggests I can achieve the same end with less work and
using an arguably clearer approach via enable_if. Overloading my
forwarding template as follows works for me with VC10 and gcc 4.5 (on
simple tests):

template<typename T>
typename std::enable_if<
!std::is_pointer<

fwd(T&& param)
{
std::cout << "General forwarding template => ";
f(std::forward<T>(param));
}

template<typename T>
typename std::enable_if<
std::is_pointer<

fwd(T&& param)
{
std::cout << "Pointer forwarding template => ";
f(std::forward<T>(param));
}

Am I overlooking a drawback to this technique?

That's the natural solution, but it doesn't scale so nicely if you
want to have several specializations. Besides, you need to know all
specializations in advance. Hence the solutions using a traits class
or a dispatcher class or...
While playing around with this, I was surprised to find that there is no
disable_if in C++0x. Yes, there's no need for it (as shown above), but
it has an established usage history (e.g., in Boost).

I didn't even know it was in boost. It just seems so natural to negate
the argument...
 
S

Scott Meyers

I didn't even know it was in boost. It just seems so natural to negate
the argument...

As far as I know, it's been around pretty much since the beginning. The
first time I read about enable_if was in the 2003 C/C++ Users Journal
article (available at http://drdobbs.com/cpp/184401659 ), and disable_if
was introduced in the same sentence as enable_if.

Scott
 
S

SG

Scott Meyers  wrote:
[...] but it doesn't scale so nicely if you
want to have several specializations. Besides, you need to know all
specializations in advance.

Exactly. This is because there is no partial ordering for multiple
matches since these "constraints" are not considered during overload
resolution.

Concepts indented to fix that by concept-based overloading where the
partial ordering is established via concept inheritance. One work
around for the lack of concept-based overloading is tag dispatching.
But this also requires one additional layer of indirection and is not
any better than the approaches that have been shown here already.

SG
 
N

Noah Roberts

While playing around with this, I was surprised to find that there is no
disable_if in C++0x. Yes, there's no need for it (as shown above), but
it has an established usage history (e.g., in Boost). Does anybody
happen to know why it's not in C++0x?

Actually, there's a significant difference between std::enable_if and
boost::enable_if that may be the main reason why disable_if is in boost
and not std.

std::enable_if< !is_pointer<T>::value ... >

That's ok.

boost::enable_if< !is_pointer<T>::value ... >

Not ok. You either need to use enable_if_c or something like so:

boost::enable_if< boost::mpl::not_< is_pointer<T> > ... >

Since "metafunction" didn't make it into the standard that I know of it
would be hard to specify an enable_if that used one. Since its easy to
negate a value, compared to a bool metafunction, it could be that the
committee simply didn't see it as vital while it's certainly a benefit
in boost. Once the decision was made to implement in terms of value
rather than metafunction it probably just fell out.
 

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

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top