On 28/02/2015 21:59,
via....@googlemail.com wrote:
>
> Apologies if this has been posted before. If there is a similar topic,
> please point me to it.
>
> Consider the following.
>
> #include <functional>
>
> struct A {};
> struct B {};
> struct C {};
>
> void f(std::function<void(A, B, C)> g) { g(A(), B(), C()); }
>
> void g(A, B, C) {}
>
> Now, I can do this:
>
> f(g); // (*)
>
> If I further define
>
> struct D
> {
> void g(A, B, C) {}
> };
>
>
> I could naively hope that the following trivial modification of (*) would
> work:
>
> D d;
> f(d.g);
>
> It does not (more on that later). The intent, I believe, is fairly obviou=
s:
> I want to pass the closure of d with D::g() to f().
>
> I can achieve the goal via this:
>
> D d;
> f([&d](A a, B, C c){d.g(a, b, c);});
>
> I dislike this, however, because it is quite verbose and violates the DRY
> principle quite blatantly.
>
> Another possible option is:
>
> D d;
> f(std::bind(&D::g, d, std::placeholders::_1, std::placeholders::_2,
> std::placeholders::_3));
>
> The problem with this other option is that it has taken me a few minutes =
of
> trial, error and intense staring at the docs. And I had to read some more
> docs just to understand whether this syntax would be functionally
> equivalent to the previous option if g() were declared virtual and D were=
a
> part of some class hierarchy. Let's face it, that thing is way too
> complicated given the simplicity of the need. And DRY is still being
> violated, because I need to spell out the name D and use three place
> holders.
>
> Another problem is that now, having at least two different ways of doing
> what I need, I would like to understand the tradeoffs involved. Which I
> cannot, because both approaches eventually utilize something that is
> expressly left unspecified by the standard.
>
> At which point a sane person would say: if both methods invoke some
> compiler magic, then perhaps it is the compiler that should figure out
> which magic works better, and so it should magically transform the naive
> f(d.g) into the appropriate implementation. Which it cannot do, because
> f(d.g) is expressly forbidden by the standard. Quoting draft n3797,
>
5.2.5.4:
>
> -- Otherwise, if E1.E2 refers to a non-static member function and the typ=
e
> of E2 is "function of parameter-type-list cv ref-qualifieropt returning T=
",
> then E1.E2 is a prvalue. The expression designates a non-static member
> function. The expression can be used only as the left-hand operand of a
> member function call (9.3).
>
> [end quote]
>
> So d.g is quite explicitly forbidden unless followed by a function call
> operator.
>
> f(&d.g) does not work either, because the d.g is still not followed by a
> function call operator, and also because d.g is a prvalue, also specified
> by 5.2.5.4, and clause 5.3.1.3 (that specifies the semantics of &) works
> with either lvalues or qualified IDs, i.e., D::g, not d.g.
>
> I understand why such use was forbidden prior to C++11: there was no
> closure support built into the language. But it exists now. As far as I c=
an
> tell, the compiler IN PRINCIPLE, upon encountering d.g (or pd->g, where p=
d
> is a pointer to D) and understanding that g is a non-static member functi=
on
> (which it has to do anyway, if only to handle the function call operator
> that must follow, or emit an error message otherwise) could more or less
> mechanically transform that code into the code similar to what I wrote
> above (or do something much smarter).
>
> Is there any fundamental reason why this could not be done? I realize tha=
t
> there are complications related to overloaded and template member
> functions; but that same sort of complications is also dealt with when th=
e
> address of a non-member function is taken.
>
> Would that contradict the standard in some major way?
>
> Thanks in advance.
>
>
>
>
One complication I have skipped over below is how to disambiguate any
overloading of the member name D::g.
A key consideration that must never be forgotten in such decisions
is not to over-complicate the simplified mental language model that
language users (humans) need to memorize in order to write correct
programs in the language. This is the (unwritten) simplified model
that is instantiated by the more detailed specification in the language
standard.
A major issue is that the expression (&d.g) is already a valid
expression of the type "pointer to member function of class D with a
certain prototype", raising the tricky question of where in the
expression semantics, this value needs to be contextually changed from
a value type that does not include the actual value of "&d", to one
that does. One way would be to define an implicit conversion from the
(unnamed) type "pointer to closure with object at address p" to
"unbound pointer to member function of class D", then redefine the
expression (&d.g) to always be the closure function pointer, but with a
hard requirement that compilers must optimize away the bind-unbind
sequence within a single expression even in un-optimized debug builds.
A further complication is that sane compilers (even if not required by
the standard) will accept the expressions
(&((reinterpret_cast<D*>(nullptr))->g))
and
(&((reinterpret_cast<D*>(AnyInvalidPtr))->g))
as equivalent to (&D::g), but cannot turn that into a valid closure
referring to the invalid class D objects. (In practice, such
expressions tend to occur inside complex macro or template expansions
that look for properties of members independently of their object).
Specifically, the issue is to establish sane rules as to when the
compiler should omit a diagnostic about binding to an invalid object
pointer, giving the conflicting requirements of pointing directly to
the offending expression in the source and postponing the non-use of
the pointer to a later optimization pass. A very similar issue has in
the past caused at least one major compiler to issue spurious
diagnostics from the implementation of things like offsetof(D,x) in
terms of a null D*.
What I don't know about, but which might exist either in a standard C++
version or in e.g. boost, is why there isn't an operator/function such
as example::bind2(&d, &d.g), which uses template or compiler magic to
determine the types of (*(&d)) and (&d.g), then return a bound function
with the same prototype as d.g (except for the hidden this pointer
argument). ("example::" is obviously a placeholder for the relevant
namespace, such as "std::" or "boost::").
Maybe something like (NOT SYNTAX CHECKED, rough sketch)
namespace example {
template<class C, class R> inline
(R (*)(void)) bind2(C *pObj, R (C::*f)(void)) {
return std::bind(f, *pObj);
}
template<class C, class R, class A1> inline
(R (*)(A1)) bind2(C *pObj, R (C::*f)(A1)) {
return std::bind(f, *pObj, A1);
}
template<class C, class R, class A1, class A2> inline
(R (*)(A1,A2)) bind2(C *pObj, R (C::*f)(A1,A2)) {
return std::bind(f, *pObj, A1, A2);
}
// Etc. for all the argument list shapes, including variadics,
// There shall also be repetitions for cv-qualified objects with
// cv-qualified member function pointers.
// There shall also be repetitions for those argument/return types
// not representable by a "class X" template argument.
// For compilers with prototype-modifying compiler specific
// function attributes, this shall all be repeated for each valid
// combination of such.
} // namespace example
Enjoy
Jakob
--
Jakob Bohm, CIO, Partner, WiseMo A/S.
https://www.wisemo.com
Transformervej 29, 2860 S=C3=B8borg, Denmark. Direct
+45 31 13 16 10
This public discussion message is non-binding and may contain errors.
WiseMo - Remote Service Management for PCs, Phones and Embedded