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