Topic: C++0x: polymorphic function NOT usable with move-only parameters (library defect?)
Author: SG <s.gesemann@gmail.com>
Date: Mon, 27 Apr 2009 14:30:41 CST Raw View
On 21 Apr., 23:54, SG <s.gesem...@gmail.com> wrote:
> Hi!
>
> Section 20.7.16 [func.wrap] of the current draft N2857.pdf (page
> 585ff) seems to impose unnecessary restrictions for the argument
> types (copy constructible instead of move constructible). Here's
> an excerpt:
>
> template<Returnable R, CopyConstructible... ArgTypes>
> class function<R(ArgTypes...)> {
> .....
> R operator()(ArgTypes... args) const;
> .....
> };
>
> It looks to me like an oversight because I can't think of any
> reason to prefer "CopyConstructible... Args" over
> "MoveConstructible... Args". With moveable-only types like
> unique_ptr it should also work with the help of forwarding
> parameters via "std::forward<Args> (args)...".
Here's an update:
It was originally introduced in N2322.pdf as
template<MoveConstructible R, MoveConstructible... ArgTypes>
class function<R(ArgTypes...)>;
and later changed in N2622.pdf to the form quoted above. It seams
there might be a compelling reason to prefer CopyConstructible as a
constraint. But I also noticed that comparison functions are *still*
defined in N2857.pdf in terms of the old constraints
template<MoveConstructible R, MoveConstructible... ArgTypes>
bool operator==(const function<R(ArgTypes...)>&, nullptr_t);
template<MoveConstructible R, MoveConstructible... ArgTypes>
bool operator!=(nullptr_t, const function<R(ArgTypes...)>&);
.....
So, there's a bit of an inconsistency in the draft w.r.t. to the
templates' constraints.
I claim that neither CopyConstructible nor MoveConstructible is
appropriate for constraining the parameter types. Even the use of
MoveConstructible constitutes overconstraining since an lvalue
reference does not satisfy this concept. See N2857.pdf for a
definition of MoveConstructible<T>. It includes an associated
requirement of the form Constructible<T,RvalueOf<T>::type>. For a non-
reference type S and T=S& this leads to the associated requirement
Constructible<S&,S&&>. This concept is not satisfied because it
involves an implicit constructor requirement (allowing implicit
conversions) but an rvalue reference is not implicitly convertible to
an lvalue reference => an lvalue reference is not MoveConstructible.
Since CopyConstructible is a refinement of MoveConstructible the
current form does not allow lvalue references nor move-only types as
parameter types.
Invoking a function (forwarding parameters) is all that's being done
in the wrapper's function call operators the Callable constraint
should be enough. Here's a sketch for a simple implementation using
constrained templates and Callable as the only constraint that
mentions the argument types:
template<Returnable R, typename ... Args>
class awrapper { // abstract base class
public:
virtual ~awrapper() {}
virtual awrapper* clone() const = 0;
virtual R operator()(Args...) = 0;
};
template<CopyConstructible F, Returnable R, typename ... Args>
requires call = Callable<F&,Args&&...>
&& Convertible<call::result_type,R>
class cwrapper : public awrapper<R,Args...> {
...
F func;
...
R operator()(Args ... args) {
return func(forward<Args>(args)...);
}
...
};
template<Returnable R, typename ... Args>
requires call = Callable<awrapper<R,Args...>&,Args&&...>
&& SameType<call::result_type,R>
class function<R(Args...)> {
.....
typedef awrapper<R,Args...> wrapper_t;
wrapper_t* pwrapper;
.....
template<typename ... Args2>
requires call2 = std::Callable<wrapper_t&,Args2&&...>
&& SameType<call2::result_type,R>
R operator()(Args && ... args2) {
return (*pwrapper)(forward<Args2>(args2)...);
}
.....
};
The reason for making the function call operator in function<> a
member template is the reduction of copies/moves for non-reference
parameter types ("perfect forwarding" works at least at this stage).
Comments?
Cheers!
SG
--
[ 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: SG <s.gesemann@gmail.com>
Date: Tue, 21 Apr 2009 15:54:27 CST Raw View
Hi!
Section 20.7.16 [func.wrap] of the current draft N2857.pdf (page
585ff) seems to impose unnecessary restrictions for the argument types
(copy constructible instead of move constructible). Here's an excerpt:
template<Returnable R, CopyConstructible... ArgTypes>
class function<R(ArgTypes...)> {
...
R operator()(ArgTypes... args) const;
...
};
It looks to me like an oversight because I can't think of any reason
to prefer "CopyConstructible... Args" over "MoveConstructible...
Args". With moveable-only types like unique_ptr it should also work
with the help of forwarding parameters via "std::forward<Args>
(args)...".
Am I wrong? What's the rationale behind the use of CopyConstructible
here?
Also, I think it's possible to avoid some unnecessary moves/copies by
replacing function<>::operator() with a function template like this:
template<typename... Args2>
requires Callable< /implementation-detail/, Args2...>
&& SameType<Callable< /implementation-detail/,
Args2...>::result_type,R>
R operator()(Args2&&... args) const;
which uses perfect forwarding. After all, that's what it's for.
Cheers!
SG
--
[ 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: SG <s.gesemann@gmail.com>
Date: Wed, 22 Apr 2009 13:10:03 CST Raw View
On 21 Apr., 23:54, SG <s.gesem...@gmail.com> wrote:
>
> template<Returnable R, CopyConstructible... ArgTypes>
> class function<R(ArgTypes...)> {
> ...
> R operator()(ArgTypes... args) const;
> ...
> };
>
> It looks to me like an oversight because I can't think of any reason
> to prefer "CopyConstructible... Args" over "MoveConstructible...
> Args". With moveable-only types like unique_ptr it should also work
> with the help of forwarding parameters via "std::forward<Args>
> (args)...".
>
> Am I wrong? What's the rationale behind the use of CopyConstructible
> here?
I think I know the answer. With the NEW rvalue reference rules
(N2844) the use of "MoveConstructible... Args" is totally fine.
BEFORE this language change lvalues were allowed to bind to rvalue
references. This made the use of "std::forward<Args>(args)..." unsafe
in this context which is why the arguments needed to be
CopyConstructible.
Here's why:
struct S {};
function<void(S&&)> k = ...;
S lvalue;
k(s); // was allowed before
After instantiating the template you'd have a function like this
void function<void(S&&)>::operator()(S&& s)
{
wrapper->doit(s);
}
The idea to support move-only types via std::forward ...
void function<void(S&&)>::operator()(S&& s)
{
wrapper->doit(std::forward<S&&>(s));
}
...turns out to be a bad idea because it may create an unnamed rvalue
reference to an object that's actually an lvalue. So, the use of
std::forward was not allowed in pre-N2844 C++.
Since N2844 seems to be accepted in principle I propose to change the
parameter requiements for polymorphic functions from
"CopyConstructible" to "MoveConstructible".
Cheers!
SG
--
[ 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 ]