cppfront
cppfront copied to clipboard
[BUG] Captured functor requires parenthesis around it to be interpreted correctly.
Describe the bug When using a functor inside a lambda, e.g.
std::array<std::string, 10> arr;
f: MyFunctor = ("Some initial value");
std::ranges::generate(arr, :() f&$*(););
This fails with some incomprehensible error saying is not invocable.
But std::ranges::generate(arr, :() (f&$*)();); works fine.
To Reproduce
Here's the code that fails (this is a toy example, ignore the bugs):
Letters: type = {
str: std::string;
i: size_t;
operator=: (out this, str_: std::string) = {
str = str_;
i = 0;
}
operator(): (inout this) -> char = str[i++];
}
main: () = {
arr: std::array<char, 10> = ();
letters: Letters = ("This text would get copied letter by letter");
std::ranges::generate(arr, :() letters&$*());
}
Here's the code that works:
Letters: type = {
str: std::string;
i: size_t;
operator=: (out this, str_: std::string) = {
str = str_;
i = 0;
}
operator(): (inout this) -> char = str[i++];
}
main: () = {
arr: std::array<char, 10> = ();
letters: Letters = ("This text would get copied letter by letter");
std::ranges::generate(arr, :() (letters&$*)());
}
I would have expected the first version to work, but failing that, I would have expected a better error message.
Here's the actual error message:
...: In lambda function:
...: error: expected primary-expression before ‘{’ token
278 | std::ranges::generate(arr, :() letters&$*());
| ^
...: error: expected ‘;’ before ‘{’ token
278 | std::ranges::generate(arr, :() letters&$*());
| ^
| ;
...: In function ‘void fill()’:
...: error: no match for call to ‘(const std::ranges::__generate_fn) (std::array<char, 10>, fill()::<lambda()>)’
278 | std::ranges::generate(arr, :() letters&$*());
| ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~
In file included from /usr/include/c++/14.2.1/algorithm:63,
from /usr/include/cpp2util.h:257,
from /home/feature/projects/cpp2/ex1/build/_cppfront/main.cpp:6:
/usr/include/c++/14.2.1/bits/ranges_algo.h:947:7: note: candidate: ‘template<class _Out, class _Sent, class _Fp> requires (input_or_output_iterator<_Out>) && (sentinel_for<_Sent, _Out>) && (copy_constructible<_Fp>) && ((invocable<_Fp&>) && (indirectly_writable<_Out, typename std::invoke_result<_Fp&>::type>)) constexpr _Out std::ranges::__generate_fn::operator()(_Out, _Sent, _Fp) const’
947 | operator()(_Out __first, _Sent __last, _Fp __gen) const
| ^~~~~~~~
/usr/include/c++/14.2.1/bits/ranges_algo.h:947:7: note: candidate expects 3 arguments, 2 provided
/usr/include/c++/14.2.1/bits/ranges_algo.h:957:7: note: candidate: ‘template<class _Range, class _Fp> requires (copy_constructible<_Fp>) && ((invocable<_Fp&>) && (output_range<_Range, typename std::invoke_result<_Fp&>::type>)) constexpr std::ranges::borrowed_iterator_t<_Range> std::ranges::__generate_fn::operator()(_Range&&, _Fp) const’
957 | operator()(_Range&& __r, _Fp __gen) const
| ^~~~~~~~
/usr/include/c++/14.2.1/bits/ranges_algo.h:957:7: note: template argument deduction/substitution failed:
/usr/include/c++/14.2.1/bits/ranges_algo.h:957:7: note: constraints not satisfied
In file included from /usr/include/c++/14.2.1/compare:40,
from /usr/include/c++/14.2.1/bits/stl_pair.h:65,
from /usr/include/c++/14.2.1/bits/stl_algobase.h:64,
from /usr/include/c++/14.2.1/algorithm:60:
/usr/include/c++/14.2.1/concepts: In substitution of ‘template<class _Range, class _Fp> requires (copy_constructible<_Fp>) && ((invocable<_Fp&>) && (output_range<_Range, typename std::invoke_result<_Fp&>::type>)) constexpr std::ranges::borrowed_iterator_t<_Range> std::ranges::__generate_fn::operator()(_Range&&, _Fp) const [with _Range = std::array<char, 10>; _Fp = fill()::<lambda()>]’:
...: required from here
278 | std::ranges::generate(arr, :() letters&$*());
| ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~
/usr/include/c++/14.2.1/concepts:360:13: required for the satisfaction of ‘invocable<_Fp&>’ [with _Fp = fill::._anon_942]
/usr/include/c++/14.2.1/concepts:360:25: note: the expression ‘is_invocable_v<_Fn, _Args ...> [with _Fn = fill::._anon_942&; _Args = {}]’ evaluated to ‘false’
360 | concept invocable = is_invocable_v<_Fn, _Args...>;
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
See also #748.
The bug is result of commit 94bea673583991d2ff90de96a8c4d7a0d312a2b6 (modified by commit a18d22ed96c7cf90e6643714515a1ff990cbb720). I'll see if reverting it in #927 fixes the issue (it should!).
Done? You still need the parentheses since commit 5663493860a5558a3d64c59ac9ee6f0e26dedf99, otherwise it's parsed as a multiplication.
Note that commit 94bea673583991d2ff90de96a8c4d7a0d312a2b6
made the rhs of the * lower to {}.
Operators don't accept a braced-init-list argument.
But commit 5663493860a5558a3d64c59ac9ee6f0e26dedf99 happened before,
which changed the * from dereference to multiplication.
Should this letters&$*() be a valid call?
I've been looking at this issue and it seems there are some cases where it's impossible to know what is the intent without checking the context.
For example in this case: res := var * (a + b);
Is this a function call on a pointer to function/functor or a multiplication?
It is impossible to know unless we look at the type of var. If it is some sort of pointer we can treat it as a function call, otherwise multiplication, but do we want to have this feature? In a big codebase it will be very confusing to understand that line properly if it can have double meaning.
I believe it is best to clarify the call by adding the necessary parenthesis res := (var*)(a + b);. This is similar to C++ where a similar code is malformed auto res = *var(a + b); due to operator precedence, requiring the same parenthesis to compile.
Maybe the bug should be closed, otherwise it needs clarification on the next course of action.