https://gcc.gnu.org/bugzilla/show_bug.cgi?id=116038
Bug ID: 116038
Summary: [14/15 Regression] ambiguous overload in bind_front
caused by deducing this
Product: gcc
Version: 14.1.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: c++
Assignee: unassigned at gcc dot gnu.org
Reporter: valentin at tolmer dot fr
Target Milestone: ---
The following snippet compiles well until 13.3 and breaks with 14.1 (and
trunk), where it complains of ambiguous overload:
$ cat <source>
#include<functional>
template<class... Ts>
struct overloaded : Ts... { using Ts::operator()...; };
struct A{};
struct B{};
int apply_a(int i, A) { return i; }
int apply_b(int i, B) { return i; }
int main() {
overloaded{
std::bind_front(apply_a, 1),
std::bind_front(apply_b, 2),
}(A());
return 0;
}
$ g++ -std=c++2b <source>
In file included from <source>:1:
/opt/compiler-explorer/gcc-trunk-20240722/include/c++/15.0.0/functional: In
instantiation of 'static constexpr decltype(auto) std::_Bind_front<_Fd,
_BoundArgs>::_S_call(_Tp&&, std::index_sequence<_Is ...>, _CallArgs&& ...)
[with _Tp = overloaded<std::_Bind_front<int (*)(int, A), int>,
std::_Bind_front<int (*)(int, B), int> >; long unsigned int ..._Ind = {0};
_CallArgs = {A}; _Fd = int (*)(int, A); _BoundArgs = {int};
std::index_sequence<_Is ...> = std::integer_sequence<long unsigned int, 0>]':
/opt/compiler-explorer/gcc-trunk-20240722/include/c++/15.0.0/functional:947:18:
required from 'constexpr std::invoke_result_t<decltype
(forward_like<_Self>(declval<_Fd>())), decltype
(forward_like<_Self>(declval<_BoundArgs>()))..., _CallArgs ...>
std::_Bind_front<_Fd, _BoundArgs>::operator()(this _Self&&, _CallArgs&& ...)
[with _Self = overloaded<std::_Bind_front<int (*)(int, A), int>,
std::_Bind_front<int (*)(int, B), int> >; _CallArgs = {A}; _Fd = int (*)(int,
A); _BoundArgs = {int}; std::invoke_result_t<decltype
(forward_like<_Self>(declval<_Fd>())), decltype
(forward_like<_Self>(declval<_BoundArgs>()))..., _CallArgs ...> = int; decltype
(forward_like<_Self>(declval<_Fd>())) = int (*&&)(int, A)]'
947 | return _S_call(std::forward<_Self>(__self), _BoundIndices(),
| ~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
948 | std::forward<_CallArgs>(__call_args)...);
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:12:8: required from here
9 | overloaded{
| ~~~~~~~~~~~
10 | std::bind_front(apply_a, 1),
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
11 | std::bind_front(apply_b, 2),
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
12 | }(A());
| ~^~~~~
/opt/compiler-explorer/gcc-trunk-20240722/include/c++/15.0.0/functional:1018:53:
error: request for member '_M_fd' is ambiguous
1018 | return std::invoke(std::forward<_Tp>(__g)._M_fd,
| ~~~~~~~~~~~~~~~~~~~~~~~^~~~~
/opt/compiler-explorer/gcc-trunk-20240722/include/c++/15.0.0/functional:1023:33:
note: candidates are: 'int (* std::_Bind_front<int (*)(int, B),
int>::_M_fd)(int, B)'
1023 | [[no_unique_address]] _Fd _M_fd;
| ^~~~~
/opt/compiler-explorer/gcc-trunk-20240722/include/c++/15.0.0/functional:1023:33:
note: 'int (* std::_Bind_front<int (*)(int, A),
int>::_M_fd)(int, A)'
/opt/compiler-explorer/gcc-trunk-20240722/include/c++/15.0.0/functional:1019:53:
error: request for member '_M_bound_args' is ambiguous
1019 | std::get<_Ind>(std::forward<_Tp>(__g)._M_bound_args)...,
| ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~
/opt/compiler-explorer/gcc-trunk-20240722/include/c++/15.0.0/functional:1024:55:
note: candidates are: 'std::tuple<int> std::_Bind_front<int (*)(int, B),
int>::_M_bound_args'
1024 | [[no_unique_address]] std::tuple<_BoundArgs...> _M_bound_args;
| ^~~~~~~~~~~~~
/opt/compiler-explorer/gcc-trunk-20240722/include/c++/15.0.0/functional:1024:55:
note: 'std::tuple<int> std::_Bind_front<int (*)(int, A),
int>::_M_bound_args'
Compiler returned: 1
A smaller reproduction without including the stdlib is:
$ cat <source>
template <typename Fn>
struct invoke_result {
Fn fn;
template <typename Self, typename CallArg>
void
operator()(this Self self, CallArg arg)
requires requires {self.fn(arg);}{}
};
template<class... Ts>
struct overloaded : Ts... { using Ts::operator()...; };
struct A{};
struct B{};
auto a = overloaded{invoke_result<int(*)(A)>(),
invoke_result<int(*)(B)>(),
}(A());
$ g++ -std=c++2b -fconcepts-diagnostics-depth=2 <source>
<source>:15:11: error: no match for call to '(overloaded<invoke_result<int
(*)(A)>, invoke_result<int (*)(B)> >) (A)'
13 | auto a = overloaded{invoke_result<int(*)(A)>(),
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
14 | invoke_result<int(*)(B)>(),
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~
15 | }(A());
| ~^~~~~
<source>:6:3: note: candidate: 'template<class Self, class CallArg> void
invoke_result<Fn>::operator()(this Self, CallArg) requires
requires{invoke_result<Fn>::operator()::self.fn(invoke_result<Fn>::operator()::arg);}
[with CallArg = Self; Fn = int (*)(A)]'
6 | operator()(this Self self, CallArg arg)
| ^~~~~~~~
<source>:6:3: note: template argument deduction/substitution failed:
<source>:6:3: note: constraints not satisfied
<source>: In substitution of 'template<class Self, class CallArg> void
invoke_result<int (*)(A)>::operator()(this Self, CallArg) requires
requires{invoke_result<Fn>::operator()::self.fn(invoke_result<Fn>::operator()::arg);}
[with Self = int (*)(A); CallArg = <missing>]':
<source>:15:11: required from here
13 | auto a = overloaded{invoke_result<int(*)(A)>(),
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
14 | invoke_result<int(*)(B)>(),
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~
15 | }(A());
| ~^~~~~
<source>:6:3: required by the constraints of 'template<class Fn>
template<class Self, class CallArg> void invoke_result<Fn>::operator()(this
Self, CallArg) requires
requires{invoke_result<Fn>::operator()::self.fn(invoke_result<Fn>::operator()::arg);}'
<source>:7:12: in requirements [with Self = overloaded<invoke_result<int
(*)(A)>, invoke_result<int (*)(B)> >; CallArg = A]
<source>:7:29: note: the required expression 'self.fn(arg)' is invalid, because
7 | requires requires {self.fn(arg);}{}
| ~~~~~~~^~~~~
<source>:7:27: error: request for member 'fn' is ambiguous
7 | requires requires {self.fn(arg);}{}
| ~~~~~^~
<source>:3:6: note: candidates are: 'int (* invoke_result<int (*)(B)>::fn)(B)'
3 | Fn fn;
| ^~
<source>:3:6: note: 'int (* invoke_result<int (*)(A)>::fn)(A)'
<source>:6:3: note: candidate: 'template<class Self, class CallArg> void
invoke_result<Fn>::operator()(this Self, CallArg) requires
requires{invoke_result<Fn>::operator()::self.fn(invoke_result<Fn>::operator()::arg);}
[with CallArg = Self; Fn = int (*)(B)]'
6 | operator()(this Self self, CallArg arg)
| ^~~~~~~~
<source>:6:3: note: template argument deduction/substitution failed:
<source>:6:3: note: constraints not satisfied
<source>: In substitution of 'template<class Self, class CallArg> void
invoke_result<int (*)(B)>::operator()(this Self, CallArg) requires
requires{invoke_result<Fn>::operator()::self.fn(invoke_result<Fn>::operator()::arg);}
[with Self = int (*)(B); CallArg = <missing>]':
<source>:15:11: required from here
13 | auto a = overloaded{invoke_result<int(*)(A)>(),
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
14 | invoke_result<int(*)(B)>(),
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~
15 | }(A());
| ~^~~~~
<source>:6:3: required by the constraints of 'template<class Fn>
template<class Self, class CallArg> void invoke_result<Fn>::operator()(this
Self, CallArg) requires
requires{invoke_result<Fn>::operator()::self.fn(invoke_result<Fn>::operator()::arg);}'
<source>:7:12: in requirements [with Self = overloaded<invoke_result<int
(*)(A)>, invoke_result<int (*)(B)> >; CallArg = A]
<source>:7:29: note: the required expression 'self.fn(arg)' is invalid, because
7 | requires requires {self.fn(arg);}{}
| ~~~~~~~^~~~~
<source>:7:27: error: request for member 'fn' is ambiguous
7 | requires requires {self.fn(arg);}{}
| ~~~~~^~
<source>:3:6: note: candidates are: 'int (* invoke_result<int (*)(B)>::fn)(B)'
3 | Fn fn;
| ^~
<source>:3:6: note: 'int (* invoke_result<int (*)(A)>::fn)(A)'
Compiler returned: 1
The issue can be fixed by changing the definition of the operator() to:
template <typename CallArg>
void operator()(CallArg arg)
requires requires {fn(arg);}{}
This bug comes up relatively frequently in a codebase that uses `std::visit` in
conjunction with `std::bind_front`, in perfectly innocent-looking code.