https://gcc.gnu.org/bugzilla/show_bug.cgi?id=118398
Bug ID: 118398 Summary: Resolving lambda expression decltype prioritized over the option to consider failed required concept using that type as argument Product: gcc Version: 14.2.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c++ Assignee: unassigned at gcc dot gnu.org Reporter: ing.russomauro at gmail dot com Target Milestone: --- In the following code (extended at https://godbolt.org/z/KhxWhqc75) gcc, clang, and MSC behave differently. We have three couples of overloaded function templates (func1, func1b, and func2). In each couple, the former is done to work with std::set, and the latter with std::map, respectively. The difference between func1 and func1b is that the latter uses a costantly true concept. As a consequence, in second couple (i.e. func1b), both work with std::map (the first still remains working for std::set). The difference beetween func1 and func2 is that func2 uses two requires clause, where the first assures the acceptability of a code (e.g. "it->first") before effectively use it in a lambda for latter clause. That's the topic of the second question. As you can better read in final comments-questions, gcc behaves differently between first and second couple when invoked with std::set, but this is likely related to the PR 96821. That's the topic of the third question. In any case, as topic of the first question, I am not convinced by the behvaiour of gcc on funct1<std::set>, despite it is the same as clang, because MVSC behaves differently. Anyway, I did not finish my study of the standard about details of what are the context where the compilation may fail while verifying satisfaction of a constraint, therefore I do not know whether MVSC or Clang (and gcc) is correct. #include <map> #include <set> template<typename Container, typename KeyExtractor> concept C = requires(Container c, KeyExtractor&& keyExtractor){ c.lower_bound(keyExtractor(c.begin())); }; template<typename Container, typename KeyExtractor> concept Cb = true; template<typename Container> requires C<Container, decltype([](Container::iterator it){return *it;})> void func1([[maybe_unused]] const Container& c){ std::cout << "func1 first overload\n"; } template<typename Container> requires C<Container, decltype([](Container::iterator it){return it->first;})> void func1([[maybe_unused]] const Container& c){ std::cout << "func1 second overload\n"; } template<typename Container> requires Cb<Container, decltype([](Container::iterator it){return *it;})> void func1b([[maybe_unused]] const Container& c){} template<typename Container> requires Cb<Container, decltype([](Container::iterator it){return it->first;})> void func1b([[maybe_unused]] const Container& c){} template<typename Container> requires requires(Container c){*c.begin();} && C<Container, decltype([](Container::iterator it){return *it;})> void func2([[maybe_unused]] const Container& c){} template<typename Container> requires requires(Container c){c.begin()->first;} && C<Container, decltype([](Container::iterator it){return it->first;})> void func2([[maybe_unused]] const Container& c){} int main(){ func1(std::set<int>{}); // - gcc behaves as clang about the need to resolve lambda types // before deciding for the constraint satisfaction. // - MVSC rejects second overload and selects the first one, that is, // it considers constrained failed due to unfair "it->first" code // in the lambda expression. func1(std::map<int,int>{}); // all compilers correctly select second overload. // This time, no reverse problem about "*it", and then // concept C fails for set iterators. func1b(std::set<int>{}); // - gcc wrongly considers both overloads as eligible, // ignoring that "it->first" does not work for second overload, // likely as side-effect of the fact that Cb concept resolves as true. // - clang consistently behaves as for func1. // - MVSC rejects instead second overload, exactly as for func1. //func1b(std::map<int,int>{}); // all compilers correctly consider ambiguous overloads. func2(std::set<int>{}); // all compilers selects first overload (i.e., gcc and clang // behaves differently about lambda type resolution) func2(std::map<int,int>{}); // all compilers selects second overload, as for func1. } Questions: 1) Is it correct (for func1<std::set>) to consider the need to resolve the lambda type before deciding for concept satisfaction ? MVSC does not. 2) In case previous answer is YES, is it correct that (for func1b<std::set>) the need decays for second requires clause after that the first one fails ? Seems to make sense. 3) I guess it is anyway unfair that gcc behaves differently between func1<std::set> and func1b<std::set>.