Issue 122971
Summary wrong management of parameter substitution in a concept-id
Labels new issue
Assignees
Reporter mrussoLuxoft
    In the code reported below: (https://godbolt.org/z/TYKhzqhEf)

there are three couples of overloaded function templates, with different constraints.
Let's say that in the couples of func1 and func2, the first overload is designed to
work with std::set and the second with std::map.

However, func1<std::set<int>{}> leads to an error for Clang and gcc, because lambda expressions are not immediate context.

This is correctly related to the following standard text (current draft):

[expr.prim.req.general] - p5:
"... can result in the formation of invalid types or expressions in the immediate context of its requirements ... In such cases, the requires-_expression_ evaluates to false; it does not cause the program to be ill-formed."

[temp.constr.atomic] - p3:
"To determine if an atomic constraint is satisfied, the parameter mapping and template arguments are first substituted into its _expression_. If substitution results in an invalid type or _expression_ in the immediate context of the atomic constraint, the constraint is not satisfied. ... "

[temp.deduct.general] - p9:
"When substituting into a lambda-_expression_, substitution into its body is not in the immediate context. ..."


However, for func1b<std:::set<int>{}>, gcc and Clang behave differently. Indeed, Clang still considers the error in lambda _expression_ code, whereas gcc ignores it because this time the concept Cb does not use its parameters, relying on the following standard text:

[temp.constr.normal] - p(1.4):
"The normal form of a concept-id C<A1 , A2 , ..., An > is the normal form of the constraint-_expression_ of C, after substituting A1 , A2 , ..., An for C’s respective template parameters in the parameter mappings in each atomic constraint. ..."

which means that the substitution leads to no error if a parameter is not used.


I initially supposed this was an error for gcc, and posted a potential bug for gcc [here](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=118398), but that discussion led to understand that's a problem of Clang.
gcc guys seemed to remember a known problem of Clang, but I tried to search with keywords 'concept', 'parameter', and others, and I could not find it. Hope I am not duplicating.


```
#include <iostream>
#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){
    std::cout << "func1b first overload\n";
}

template<typename Container>
requires Cb<Container, decltype([](Container::iterator it){return it->first;})>
void func1b([[maybe_unused]] const Container& c){
    std::cout << "func1b second overload\n";
}

template<typename Container>
requires requires(Container c){*c.begin();}
&& C<Container, decltype([](Container::iterator it){return *it;})>
void func2([[maybe_unused]] const Container& c)
{
    std::cout << "func2 first overload\n";
}

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)
{
    std::cout << "func2 second overload\n";
}

int main(){
    func1(std::set<int>{}); // - gcc and clang manage lambda as non-immediate
                            //   context, so getting a compilation error.
                            // - MVSC rejects second overload and selects the
                            //   first one, that is, it considers failed
                            //   constraints 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 considers both overloads as eligible,
                             //   ignoring the part "it->first" for second overload,
                             //   because it is not used in the concept definition.
                             // - clang consistently behaves instead 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 select first overload (i.e., gcc and clang
                            // behaves differently about lambda type resolution, because
                            // the result of the normal form for the constraints, is
                            // provided by a clause before the one containing the lambda)
    func2(std::map<int,int>{}); // all compilers select second overload, as for func1.
}
```


_______________________________________________
llvm-bugs mailing list
llvm-bugs@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs

Reply via email to