hsutter / cppfront

A personal experimental C++ Syntax 2 -> Syntax 1 compiler
Other
5.23k stars 224 forks source link

[BUG] Function expression (lambda) cannot capture (and call) a reference to a callable passed via forwarding reference #1096

Open bluetarpmedia opened 3 weeks ago

bluetarpmedia commented 3 weeks ago

Describe the bug A Cpp2 function expression cannot capture a reference to a callable passed via a forwarding reference (and then call it), meaning that the callable has to be copy constructed instead.

To Reproduce Run cppfront on this code:

is_even_functor: type = {
    operator=: (out this)       = { std::cout << "is_even_functor: ctor\n"; }
    operator=: (out this, that) = { std::cout << "is_even_functor: copy ctor\n"; }
    operator(): (this, x: int) -> bool = {
        return x % 2 == 0;
    }
}

ranges: namespace == std::ranges;

find_match_cpp2: (forward r, forward pred) -> bool = {
    it:= ranges::find_if(r, :(x) pred$(x));    // Copies `pred`
    //   ranges::find_if(r, :(x) pred&$(x));   // Error
    //   ranges::find_if(r, :(x) pred&$*(x));  // Error
    return it != ranges::cend(r);
}

bool find_match_cpp1(auto &&r, auto &&pred) {
    // No copy of `pred`
    auto it = ranges::find_if(r, [&pred](auto x) -> bool { return pred(x); });
    return it != ranges::cend(r);
}

main: () -> int = {
    v: std::array = (1, 2, 3, 4);
    is_even: is_even_functor = ();

    std::cout << "\nCall Cpp2\n";
    std::println("Found an even number: {}", find_match_cpp2(v, is_even));

    std::cout << "\nCall Cpp1\n";
    std::println("Found an even number: {}", find_match_cpp1(v, is_even));
    return 0;
}

Repro on Godbolt

The program output shows the undesired copy of is_even:

is_even_functor: ctor

Call Cpp2
is_even_functor: copy ctor         <--- Don't want this
Found an even number: true

Call Cpp1
Found an even number: true

The find_match_cpp1 C++ function demonstrates the desired behaviour. No copy of pred should occur.

But in find_match_cpp2 the capture of pred causes a copy:

[_0 = CPP2_FORWARD(pred)]

This Cpp2 code to capture pred by reference:

it:= ranges::find_if(r, :(x) pred&$(x));

results in this C++ capture:

[_0 = (&CPP2_FORWARD(pred))]

but the call to _0 fails since it's a pointer that needs to be dereferenced.

This Cpp2 code to capture pred by reference and then dereference it:

it:= ranges::find_if(r, :(x) pred&$*(x));

results in the same C++ capture as above (_0 = (&CPP2_FORWARD(pred)) but lowers to the following:

return _0 * (x);

which results in a C++ compiler error.

This Cpp2 code succeeds:

it:= ranges::find_if(r, :(x) std::invoke(pred&$*, x));

but is less friendly to write (IMO) than the original C++.

Additional context I was translating the ranges::adjacent_find code from cppreference:

constexpr bool some_of(auto&& r, auto&& pred) // some but not all
{
    return std::ranges::cend(r) != std::ranges::adjacent_find(r,
        [&pred](auto const& x, auto const& y)
        {
            return pred(x) != pred(y);
        });
}
bluetarpmedia commented 3 weeks ago

Also, just a note regarding the some_of function from the cppreference example.

My Cpp2 translation here:

some_of: (forward r, forward pred) -> bool == {
    return
        ranges::cend(r) !=
        ranges::adjacent_find(r, :(x, y) pred$(x) != pred$(y));
}

lowers to:

constexpr auto some_of(auto&& r, auto&& pred) -> bool
{
    return 
        ranges::cend(r) != 
        ranges::adjacent_find(r, [_0 = pred, _1 = CPP2_FORWARD(pred)](auto const& x, auto const& y) mutable -> auto { return _0(x) != _1(y);  }); 
}

So pred is copied twice. Should cppfront detect and avoid duplicating the captures?