C++ Logo

SG14

Advanced search

Subject: Re: intrinsic method binding
From: Arthur O'Dwyer (arthur.j.odwyer_at_[hidden])
Date: 2019-04-25 22:34:33


On Thu, Apr 25, 2019 at 4:23 PM Farid Mehrabi <farid.mehrabi_at_[hidden]>
wrote:

> در تاریخ پنجشنبه ۲۵ آوریل ۲۰۱۹،‏ ۱۹:۱۴ Arthur O'Dwyer <
> arthur.j.odwyer_at_[hidden]> نوشت:
>
>> On Thu, Apr 25, 2019 at 10:25 AM Farid Mehrabi via SG14 <
>> sg14_at_[hidden]> wrote:
>>
>>> Is there any interest enabling deferred call semantics for intrinsic
>>> method binding with native syntax:
>>> *std::function<signature> fn=instance.method;*
>>> IMHO due to spec of member function pointers, std::bind is expensive
>>> in terms of memory. It also has the downside of too verbose syntax which is
>>> too complicated for training novice programmers and lacks the ability to
>>> cover early bound base methods:
>>> *auto fn=instance.base::method;*
>>> And lambdas cannot help if code repetition is intended because one would
>>> need to forward all arguments:
>>> *auto lam=[&instance](Args...)*
>>> *{return instance.method(params...);};*
>>> Thanks in advance.
>>>
>>
>> This sounds very similar to the "lifting syntax" proposal briefly
>> mentioned here:
>>
>> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0573r0.html#Priors
>> and solidly proposed here:
>> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0834r0.html
>> https://blog.tartanllama.xyz/passing-overload-sets/
>>
>> Notice that `instance.method` is already an overload set:
>>
>> // https://godbolt.org/z/PQPXxa
>> struct S {
>> void method();
>> int method(int);
>> static void smethod();
>> static int smethod(int);
>> };
>> S instance;
>> int main() {
>> std::function<int(int)> f = instance.method; // fails, for the same
>> reason that
>> std::function<int(int)> g = S::smethod; // also fails
>> }
>>
>> So what you're looking for, at the moment (C++17 and presumably C++2a) is
>> currently spelled
>>
>> #define FWD(...) std::forward<decltype(__VA_ARGS__)>(__VA_ARGS__)
>>
>> #define LIFT(X) [*&*](auto &&... args) \
>> noexcept(noexcept(X(FWD(args)...))) \
>> -> decltype(X(FWD(args)...)) \
>> { \
>> return X(FWD(args)...); \
>> }
>>
>> which is the same thing Simon Brand gives in his blog post, but with one
>> additional ampersand (bolded above).
>>
>> // https://godbolt.org/z/ifa2Hr
>> int main() {
>> std::function<int(int)> f = LIFT(instance.method); // OK
>> std::function<int(int)> g = LIFT(S::smethod); // OK
>> }
>>
>> HTH,
>> –Arthur
>>
>
> Thanks for this quick yet accurate and fleshy reply. I am not sure to have
> grasped your intention from the term 'spelled'(English is my 3rd language,
> the 2nd of course you guess what is).
>

My use of "X is spelled Y" is an analogy to human language. As if you said
"I know the word I'm looking for; I'm just not sure how to spell it." I'm
saying that this particular C++ idiom is spelled `LIFT(instance.method)` —
that is, if you want to say "lift `instance.method` into a callable
object," then `LIFT(instance.method)` is how you spell it in your code so
that the average knowledgeable reader will be able to understand what
you're doing.

> If the macro **LIFT** is the conclusion, that's gonna be a sad ending. We
> need a happy ending here, and the spell shall be removed.
>

Heh. :)

> The difficulties in the direction of refrenced proposals include:
> 1. Long shot:
> They trigger an overhaul of existing featrues
> 2. Syntax modifications:
> New syntax for lift, new syntax for lambda which has already bargain ed
> massive modifications to the language, new syntax for forwarding...
>
> OTH, what I have in mind is somewhat different. I am starting small but
> with a broad look ahead. IMHO we should occupy this land one hill at a
> time. So I suggest puting the focus on lifting member function overload
> sets for now. Once the semantics are specified and accepted, it will be
> time to attack the general case of overload sets.
>

You can certainly try that strategy of attack, but FYI, I don't think it
will be very fruitful. Lifting an overload set composed of plain old
functions is already difficult and contentious. Lifting an overload set
composed of *bound member functions* has all those same difficulties, *plus
additional difficulties regarding object lifetime*. Consider:

struct A {
    int i;
    void m() {}
    int m(int i) const { return x + i; }
};
int main() {
    std::function<int(int)> fun;
    {
        A a { 1 };
        auto bound = a.m; // A: your new hypothetical syntax
        a.i = 2;
        fun = bound; // B: now capture that closure object in a
std::function
        a.i = 3;
    }
    return fun(42); // C: Eek!
}

On the line marked "A", do we "capture" a copy of `a`, or a reference to
`a`? That is, do we call `A`'s copy constructor or not?
On the line marked "B", do we call `A`'s copy constructor again?
On the line marked "C", do we return 43, or do we have undefined behavior
because we're dereferencing a dangling reference to `A a`?

Consider also:

struct B {
    int i;
    void m() {} // B1
    static int m(int i) { return x; } // C1
};
B b;
int main() {
    auto bound = b.m; // A: your new hypothetical syntax
    bound(); // B2: call a non-static method
    bound(42); // C2: call a static method
}

On the line marked `A`, do we capture a copy of `b`, or do we capture a
reference to `b`, or do we capture nothing at all (because `B b` is a
global variable, and global variables are not captured by traditional C++
lambdas)?
Suppose we do capture something. Presumably we're capturing it because we
need a `this` object in order to do the call to the member function. But
line C2 actually just calls a static method, and static methods don't need
a `this` object. Suppose we removed lines B1 and B2 — would that change the
semantics of line A and eliminate the capture?

With the `LIFT` macro, all these questions have "obvious" answers (even if
they're not always the most ergonomic answers).
With your hypothetical new syntax, you're going to have to come up with
answers, and defend your answers.

> The ' instance.method ' notation that currently can only trigger
> invocation, can be reinterpreted as a binding of a member overload set on
> an object without any tango with syntax.
>

If you pursue your syntax, you will also have to answer: Is there any
difference between
    instance.method(some, args);
and
    (instance.method)(some, args);
and if so, what is the difference?

Also, do you propose to support
    auto bound = instanceptr->method;
and if so, what if anything does it "capture"?
Is there any difference between
    auto bound = instanceptr->method;
and
    auto bound = (*instanceptr).method;
?

–Arthur

>



SG14 list run by sg14-owner@lists.isocpp.org

Older Archives on Google Groups