Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
bluetarpmedia opened this issue Jun 5, 2024 · 3 comments
Labels
bug Something isn't working

Comments

@bluetarpmedia
Copy link
Contributor

bluetarpmedia commented Jun 5, 2024

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 bluetarpmedia added the bug Something isn't working label Jun 5, 2024
@bluetarpmedia bluetarpmedia changed the title [BUG] Function expression (lambda) cannot capture a reference to a callable passed via forwarding reference [BUG] Function expression (lambda) cannot capture (and call) a reference to a callable passed via forwarding reference Jun 5, 2024
@bluetarpmedia
Copy link
Contributor Author

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?

@JohelEGP
Copy link
Contributor

JohelEGP commented Oct 4, 2024

See also #1283.

@JohelEGP
Copy link
Contributor

JohelEGP commented Oct 4, 2024

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

It does, if you discard the last use that would be a forward, and thus capture differently.
Append _ = pred; and you get a single capture.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants