cppfront icon indicating copy to clipboard operation
cppfront copied to clipboard

UFCS: function call starts UFCS chaining

Open filipsajdak opened this issue 2 years ago • 3 comments

On the stdio example there is a call to c-function:

myfile := fopen("xyzzy", "w");
myfile.fprintf( "Hello with UFCS!" );
myfile.fclose();

This is great but we can do better than that. What I don't like about this approach is the lost opportunity to save us from resource leak. What I would like to do is to have a possibility to wrap the return FILE* into something that will clean things up.

I have implemented the feature that starts UFCS chaining on the function call of syntax func1().func2() (it can go recursively further). That brings us more interesting features like wrapping things into smart pointers in one call.

#include <vector>
#include <memory>

template <typename T, typename R>
auto on_scope_exit(T* f, R(*close)(T*)) {
    return std::unique_ptr<T, decltype(close)>{f, close};
}

main: () -> int = {
    f2 := fopen("variable2.txt", "w").on_scope_exit(fclose);
    f2.get().fprintf("you can handle smart_ptrs without changing behaviour of UFCS");

    m := fopen("manual.txt", "w");
    m.fprintf("Manual handling still works");
    m.fclose();
}

That can go even further. Thanks to https://github.com/hsutter/cppfront/pull/13 the code can be even simpler:

    f2 := fopen("variable2.txt", "w").on_scope_exit(fclose);
    f2.fprintf("you can handle smart_ptrs without changing behaviour of UFCS");

    m := fopen("manual.txt", "w");
    m.fprintf("Manual handling still works");
    m.fclose();

    f := fopen("variable.txt", "w").on_scope_exit(fclose);
    f.fprintf("This one use variable and\n");
    f.fprintf("    separate\n");
    f.fprintf("        calls\n");
    f.fprintf("            of fprintfs");

    fopen("one_liner.txt", "w").on_scope_exit(fclose)
                               .fprintf("This is oneliner!!\n\nAnd it just close automatically");
}

The last use case (one-liner) makes me think that UFCS brings some risk of leaking resources when chained. When results of fopen will be used by fprintf directly the pointer to file will be lost.

    // bad use
    fopen("one_liner.txt", "w").fprintf("there is a leak on the end of this function");

I will investigate if there is a possibility to block potentially leaking combinations. Smart pointers are safe and we can eliminate cases when rvalue pointers are passed to a chained function.

filipsajdak avatar Sep 26 '22 00:09 filipsajdak

    // bad use
    fopen("one_liner.txt", "w").fprintf("there is a leak on the end of this function");

I will investigate if there is a possibility to block potentially leaking combinations.

https://github.com/isocpp/CppCoreGuidelines/blob/master/docs/Lifetime.pdf should take care of that. It mentions:

Additionally, for convenient adoption without modifying existing standard library headers, the following well-known standard types are treated as-if annotated as Owners: stack, queue, priority_queue, optional, variant, any, and regex.

It could do similarly for fopen. If it were my_fopen instead, I think you're out of luck if you don't wrap it safely.

JohelEGP avatar Sep 26 '22 01:09 JohelEGP

Another interesting extension can be proposed to make calling functions like fprintf that are not returning FILE* and stops the chaining.

visit: (in v:_, in callable:_) -> auto = {
    return callable(v);
}

main: () -> int = {
    fopen("one_liner.txt", "w").on_scope_exit(fclose).get().visit(:(e:_) = {
        e.fprintf("1) This is oneliner!!\n\n");
        e.fprintf("2) This is oneliner!!\n\n");
        e.fprintf("3) This is oneliner!!\n\n");
        e.fprintf("4) This is oneliner!!\n\n");
        e.fprintf("5) This is oneliner!!\n\n");
        e.fprintf("6) This is oneliner!!\n\n");
    });
}

filipsajdak avatar Sep 29 '22 06:09 filipsajdak

(Repeating from #17) Interim ack: Thanks! Yes, I've been meaning to add chaining. I was thinking of implementing it later in the same function and remove this special case part of the function (I dislike that there are two paths that deal with . operator differently)... let me keep this PR open without action until I get a chance to look at it again later.

hsutter avatar Oct 01 '22 23:10 hsutter

Picking this up: I'm willing to consider UFCS chaining, even as an additional section within the function. Before I look at this more closely... do you want to pursue this now? if yes does it need to be rebased including to mirror the updates I just made to the current UFCS code (we want to stay consistent)? Thanks!

hsutter avatar Dec 16 '22 23:12 hsutter

Adding bidirectional xrefs: How does this relate to #17?

hsutter avatar Dec 16 '22 23:12 hsutter

This is for a case when there is a free function call and the chaining starts on the function result.

#17 is for the cases when the first call is done on the object by calling its method.

filipsajdak avatar Dec 17 '22 00:12 filipsajdak

I am pretty sure it needs to be rebased. I can do that tomorrow.

filipsajdak avatar Dec 17 '22 00:12 filipsajdak

This PR is replaced by: https://github.com/hsutter/cppfront/pull/169

filipsajdak avatar Dec 20 '22 01:12 filipsajdak