Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wrong management of parameter substitution in a concept-id #122971

Open
mrussoLuxoft opened this issue Jan 14, 2025 · 1 comment
Open

wrong management of parameter substitution in a concept-id #122971

mrussoLuxoft opened this issue Jan 14, 2025 · 1 comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" concepts C++20 concepts

Comments

@mrussoLuxoft
Copy link

mrussoLuxoft commented Jan 14, 2025

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{}> leads to an error for Clang and gcc, due to the second overload, 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{}>, 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, 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.
}
@EugeneZelenko EugeneZelenko added clang:frontend Language frontend issues, e.g. anything involving "Sema" concepts C++20 concepts and removed new issue labels Jan 14, 2025
@llvmbot
Copy link
Member

llvmbot commented Jan 14, 2025

@llvm/issue-subscribers-clang-frontend

Author: None (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, due to the second overload, 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, 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 &lt;iostream&gt;
#include &lt;map&gt;
#include &lt;set&gt;

template&lt;typename Container, typename KeyExtractor&gt;
concept C = requires(Container c, KeyExtractor&amp;&amp; keyExtractor){
    c.lower_bound(keyExtractor(c.begin()));
};

template&lt;typename Container, typename KeyExtractor&gt;
concept Cb = true;

template&lt;typename Container&gt;
requires C&lt;Container, decltype([](Container::iterator it){return *it;})&gt;
void func1([[maybe_unused]] const Container&amp; c){
    std::cout &lt;&lt; "func1 first overload\n";
}

template&lt;typename Container&gt;
requires C&lt;Container, decltype([](Container::iterator it){return it-&gt;first;})&gt;
void func1([[maybe_unused]] const Container&amp; c){
    std::cout &lt;&lt; "func1 second overload\n";
}

template&lt;typename Container&gt;
requires Cb&lt;Container, decltype([](Container::iterator it){return *it;})&gt;
void func1b([[maybe_unused]] const Container&amp; c){
    std::cout &lt;&lt; "func1b first overload\n";
}

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

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

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

int main(){
    func1(std::set&lt;int&gt;{}); // - 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-&gt;first" code
                            //   in the lambda expression.
    func1(std::map&lt;int,int&gt;{}); // all compilers correctly select second overload.
                                // This time, no reverse problem about "*it",
                                // and then concept C fails for set iterators.

    func1b(std::set&lt;int&gt;{}); // - gcc considers both overloads as eligible,
                             //   ignoring the part "it-&gt;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&lt;int,int&gt;{}); // all compilers correctly consider ambiguous overloads.

    func2(std::set&lt;int&gt;{}); // 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&lt;int,int&gt;{}); // all compilers select second overload, as for func1.
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" concepts C++20 concepts
Projects
None yet
Development

No branches or pull requests

3 participants