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>.

Reply via email to