Topic: Perfect Forwarding Failure Cases


Author: Scott Meyers <NeverRead@aristeia.com>
Date: Sat, 16 Jan 2010 10:41:35 CST
Raw View
Several comments on a recent blog entry by David Abrahams (
http://cpp-next.com/archive/2009/12/onward-forward/ ) discussed places where
perfect forwarding fails.  That is, given an expression e and a function f that
will accept e,

    f(e);       // okay

a perfect-forwarding wrapper w for f won't behave the same, i.e.,

    w(e);       // doesn't behave the same as f(e)

Here are the examples from the blog comments.

Example #1 (from Eelis):

    Suppose we wish to forward to the following function:

      void f(void(*)(int));

    Applying the idiom, we write the wrapper as follows:

      template <typename Arg> void wrap(Arg && x)
      { f(forward<Arg>(x)); }

    Now suppose have the following template:

      template <typename T> void g(T);

    Then we can call f with g:

      f(g);    // ok!

    However, the wrapper cannot be called with g:

      wrap(g); // error: cannot resolve overload

    One uses exactly this conversion [from function template name to function
    pointer] every time one uses   cout << endl   (or other stream manipulators)!
    Indeed, that is the context in which I originally encountered this problem.

Example #2 (from Matt Calabrese):

    There is a more common case that fails that I  m sure plenty of people will
    accidentally bump into:

      void foo( int* );

    template< typename ParamType >
    void bar( ParamType&& arg )
    {
      foo( std::forward< ParamType >( arg ) );
    }

    int main()
    {
      foo( 0 ); // Fine
      bar( 0 ); // Kaboom
    }

To these we can add one I posted about some time ago (
http://tinyurl.com/yzr22jh ): braced initializer lists can't be perfectly
forwarded, because templates (unlike auto, sigh) are not permitted to deduce a
type for a braced initializer list.

So perfect forwarding doesn't work in at least these cases:
- When the expression passed is a function template name.
- When the expression passed is the literal 0 to be used as a null
    pointer constant.
- When the expression is a braced initializer list. (I see that draft C++0x
    explicitly remarks in 5.1.2/4 that a braced-init-list is not an
    expression, but such lists can be passed as function arguments, and 5.2.2/1
    says that a function call takes a comma-separated "list of expressions."
    Anyway, perfecting forwarding fails for arguments that are braced initializer
    lists.)

Are there other cases where perfect forwarding is something less than perfect?
It'd be nice to have a comprehensive list.

Thanks,

Scott


--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Johannes Schaub (litb)" <schaub-johannes@web.de>
Date: Sun, 17 Jan 2010 01:10:40 CST
Raw View
Scott Meyers wrote:

> Several comments on a recent blog entry by David Abrahams (
> http://cpp-next.com/archive/2009/12/onward-forward/ ) discussed places
> where
> perfect forwarding fails.  That is, given an expression e and a function f
> that will accept e,
>
>     f(e);       // okay
>
> a perfect-forwarding wrapper w for f won't behave the same, i.e.,
>
>     w(e);       // doesn't behave the same as f(e)
>
> Here are the examples from the blog comments.
>
> [ snippet ]
>
> Are there other cases where perfect forwarding is something less than
> perfect? It'd be nice to have a comprehensive list.
>

One that's bugging me. Consider:

void f(int);

template<typename T>
void g(T &&t) { f(forward<T>(t)); }

Someone wrote classes, and made use of inline initialization of static
constants for integers:

struct A {
 static int const size = 255;
};

Of course, calling f(A::size); is alright. But doing the same with
g(A::size) is not, because then we need to provide a definition (since its
address is touched by the reference parameter of the forwarder).


--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use
mailto:std-c++@netlab.cs.rpi.edu<std-c%2B%2B@netlab.cs.rpi.edu>
]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Nikolay Ivchenkov <tsoae@mail.ru>
Date: Sun, 17 Jan 2010 01:11:52 CST
Raw View
On 16 Jan, 19:41, Scott Meyers <NeverR...@aristeia.com> wrote:

> So perfect forwarding doesn't work in at least these cases:
> - When the expression passed is a function template name.

I would say: when the meaning of the argument passed shall be resolved
by the overload resolution.

> Are there other cases where perfect forwarding is something less than
perfect?

Yes.

1) An lvalue that designates a bit-field cannot be forwarded:

   #include <utility>

   struct X
   {
       signed int bitfield : 4;
   };

   void f(int) {}

   template <class Arg>
       void fwd(Arg &&arg)
           { f(std::forward<Arg>(arg)); }

   int main()
   {
       X x;
       f(x.bitfield);
       // OK

       fwd(x.bitfield);
       // ill-formed
   }

See N3000 - 14.9.2.1/3:

"If P is an rvalue reference to a cv-unqualified template parameter
and the argument is an lvalue, the type "lvalue reference to A" is
used in place of A for type deduction."

x.bitfield is an lvalue, so the type "lvalue reference to int" is used
in place of A for type deduction, and the type of the function
parameter is "lvalue reference to int".

See 8.5.3/5:

"- If the reference is an lvalue reference and the initializer
expression
-- is an lvalue (but is not a bit-field), and [...]
-- has a class type (i.e., T2 is a class type), where [...]
[...]
- Otherwise, the reference shall be an lvalue reference to a non-
volatile const type (i.e., cv1 shall be const), or the reference shall
be an rvalue reference and the initializer expression shall be an
rvalue."

The requirements of 8.5.3/5 are not satisfied, so the program is ill-
formed.


2) Undefined object cannot be forwarded:

   #include <utility>

   struct X
   {
       static int const value = 1;
   };

   void f(int) {}

   template <class Arg>
       void fwd(Arg &&arg)
           { f(std::forward<Arg>(arg)); }

   int main()
   {
       f(X::value);
       // OK

       fwd(X::value);
       // makes the program ill-formed
   }

See 3.2/2:

"An object or non-overloaded function whose name appears as a
potentially-evaluated expression is used unless it is an object that
satisfies the requirements for appearing in a constant expression
(5.19) and the lvalue-to-rvalue conversion (4.1) is immediately
applied."

and 3.2/3:

"Every program shall contain exactly one definition of every non-
inline function or object that is used in that program; no diagnostic
required."

In the first case the object X::value is not used, but in the second
case this object is used (so, the definition of X::value shall be
provided).


3) An expression of type which "includes" (there is no precise
definition for this) a type of the form "pointer to array of unknown
bound of T" or "reference to array of unknown bound of T" cannot be
forwarded:

   #include <utility>

   extern int arr[];
   int (&ref)[] = arr;
   int arr[10];

   void f(int *) {}
   template <class Arg>
       void fwd(Arg &&arg)
           { f(std::forward<Arg>(arg)); }

   int main()
   {
       f(ref);
       // OK: array-to-pointer conversion applies

       fwd(ref);
       // ill-formed
   }

Here in the second case Arg is deduced to be "lvalue reference to
array of unknown bound of int", so a function parameter type "lvalue
reference to array of unknown bound of int" is formed. Such parameter
type is prohibited by 8.3.5/8:

"If the type of a parameter includes a type of the form "pointer to
array of unknown bound of T" or "reference to array of unknown bound
of T," the program is ill-formed."


> It'd be nice to have a comprehensive list.

It's very hard to prove that any currently known list is really
comprehensive :-)

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use
mailto:std-c++@netlab.cs.rpi.edu<std-c%2B%2B@netlab.cs.rpi.edu>
]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Florian Weimer <fw@deneb.enyo.de>
Date: Sun, 17 Jan 2010 19:09:08 CST
Raw View
* Scott Meyers:

> Example #2 (from Matt Calabrese):
>
>    There is a more common case that fails that I  m sure plenty of
>    people will accidentally bump into:
>
>      void foo( int* );
>
>    template< typename ParamType >
>    void bar( ParamType&& arg )
>    {
>      foo( std::forward< ParamType >( arg ) );
>    }
>
>    int main()
>    {
>      foo( 0 ); // Fine
>      bar( 0 ); // Kaboom
>    }

Isn't this more a problem with null pointer constants which nullptr
tries to address?  Would it work here, too?

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Scott Meyers <NeverRead@aristeia.com>
Date: Mon, 18 Jan 2010 09:26:37 CST
Raw View
Florian Weimer wrote:
> Isn't this more a problem with null pointer constants which nullptr
> tries to address?  Would it work here, too?

Using nullptr would solve the problem, but that's not the issue.  The issue is
that perfect forwarding is, in this scenario, not perfect.

Scott

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Martin B." <0xCDCDCDCD@gmx.at>
Date: Tue, 19 Jan 2010 16:24:49 CST
Raw View
Scott Meyers wrote:
> Several comments on a recent blog entry by David Abrahams (
> http://cpp-next.com/archive/2009/12/onward-forward/ ) discussed places
> where
> perfect forwarding fails.  That is, given an expression e and a function
> f that
> will accept e,
>
>    f(e);       // okay
>
> a perfect-forwarding wrapper w for f won't behave the same, i.e.,
>
>    w(e);       // doesn't behave the same as f(e)
>
> Here are the examples from the blog comments.
>
> Example #1 (from Eelis):
>
>    Suppose we wish to forward to the following function:
>
>      void f(void(*)(int));
>
>    Applying the idiom, we write the wrapper as follows:
>
>      template <typename Arg> void wrap(Arg && x)
>      { f(forward<Arg>(x)); }
>
>    Now suppose have the following template:
>
>      template <typename T> void g(T);
>
>    Then we can call f with g:
>
>      f(g);    // ok!
>
>    However, the wrapper cannot be called with g:
>
>      wrap(g); // error: cannot resolve overload
>
>    One uses exactly this conversion [from function template name to
> function
>    pointer] every time one uses =C2=93cout << endl=C2=94 (or other stream
> manipulators)!
>    Indeed, that is the context in which I originally encountered this
> problem.
>
> Example #2 (from Matt Calabrese):
>
>    There is a more common case that fails that I=C2=92m sure plenty of
> people will
>    accidentally bump into:
>
>      void foo( int* );
>
>    template< typename ParamType >
>    void bar( ParamType&& arg )
>    {
>      foo( std::forward< ParamType >( arg ) );
>    }
>
>    int main()
>    {
>      foo( 0 ); // Fine
>      bar( 0 ); // Kaboom
>    }
>
> To these we can add one I posted about some time ago (
> http://tinyurl.com/yzr22jh ): braced initializer lists can't be perfectly
> forwarded, because templates (unlike auto, sigh) are not permitted to
> deduce a
> type for a braced initializer list.
>
> So perfect forwarding doesn't work in at least these cases:
> - When the expression passed is a function template name.
> - When the expression passed is the literal 0 to be used as a null
>    pointer constant.
> - When the expression is a braced initializer list. (I see that draft C++=
0x
>    explicitly remarks in 5.1.2/4 that a braced-init-list is not an
>    expression, but such lists can be passed as function arguments, and
> 5.2.2/1
>    says that a function call takes a comma-separated "list of expressions=
."
>    Anyway, perfecting forwarding fails for arguments that are braced
> initializer
>    lists.)
>
> Are there other cases where perfect forwarding is something less than
> perfect?
> It'd be nice to have a comprehensive list.
>

I do not claim to understand all the examples given, as I haven't
bothered too much with c++0x yet. But from what I can see "perfect
forwarding" does appear to happen on a different level that the one the
programmer intuitively would guess at when writing source code.
The forwarding happens on the type level, i.e. it seems to me that for
this forwarding to work a complete type (or something like that) is
needed at the top level of the forwarding chain.
So shouldn't the question rather be what actually is forwarded by a
&&-parameter, so that we could state in which cases it can only work
(and what "perfect" means)?

br,
Martin


--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use
mailto:std-c++@netlab.cs.rpi.edu <std-c%2B%2B@netlab.cs.rpi.edu>]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]