Bug 99599 - [12/13 Regression] Concepts requirement falsely reporting cyclic dependency, breaks tag_invoke pattern
Summary: [12/13 Regression] Concepts requirement falsely reporting cyclic dependency, ...
Status: SUSPENDED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 11.0
: P3 normal
Target Milestone: 12.5
Assignee: Not yet assigned to anyone
URL: https://godbolt.org/#g:!((g:!((g:!((h...
Keywords: rejects-valid
: 102065 106538 107429 108393 109397 110160 110291 (view as bug list)
Depends on:
Blocks:
 
Reported: 2021-03-15 13:24 UTC by Mark M
Modified: 2024-07-19 13:10 UTC (History)
15 users (show)

See Also:
Host:
Target:
Build:
Known to work: 10.2.0
Known to fail: 11.0
Last reconfirmed: 2021-04-01 00:00:00


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Mark M 2021-03-15 13:24:10 UTC
The following code compiles fine on GCC 10, Clang, MSVC, but fails in GCC 11/trunk:

### begin

struct foo_tag{};
struct bar_tag{};

template <class T>
concept fooable = requires(T it) {
    invoke_tag(foo_tag{}, it); // <-- here
};

template<class T>
auto invoke_tag(foo_tag, T in) {
    return in;
}

template<fooable T>
auto invoke_tag(bar_tag, T it) {
    return it;
}

int main() {
    // Neither line below compiles in GCC 11, independently of the other
    return invoke_tag(foo_tag{}, 2);
    return invoke_tag(bar_tag{}, 2);
}

### end

Produces the following compiler error:

### begin

<source>: In substitution of 'template<class T>  requires  fooable<T> auto invoke_tag(bar_tag, T) [with T = int]':
<source>:6:15:   required by substitution of 'template<class T>  requires  fooable<T> auto invoke_tag(bar_tag, T) [with T = int]'
<source>:21:35:   required from here
<source>:5:9:   required for the satisfaction of 'fooable<T>' [with T = int]
<source>:5:19:   in requirements with 'T it' [with T = int]
<source>:5:19: error: satisfaction of atomic constraint 'requires(T it) {invoke_tag({}, it);} [with T = T]' depends on itself
    5 | concept fooable = requires(T it) {
      |                   ^~~~~~~~~~~~~~~~
    6 |     invoke_tag(foo_tag{}, it);
      |     ~~~~~~~~~~~~~~~~~~~~~~~~~~
    7 | };
      | ~                  

### end

It seems that the template requirements of invoke_tag(bar_tag, int) are considered while evaluating line marked "here". Requirements of irrelevant overloads should not be considered, as it can potentially lead to falsely reporting a cyclic dependency.

This bug effectively prevents using the modern tag_invoke pattern, which is increasingly used as a customisation point in many modern libraries.
Comment 1 Jonathan Wakely 2021-03-15 14:20:32 UTC
Rejected since r11-2774

    c++: Check satisfaction before non-dep convs. [CWG2369]
    
    It's very hard to use concepts to protect a template from hard errors due to
    unwanted instantiation if constraints aren't checked until after doing all
    substitution and checking of non-dependent conversions.
    
    It was pretty straightforward to insert the satisfaction check into the
    logic, but I needed to make the 3-parameter version of
    satisfy_declaration_constraints call push_tinst_level like the 2-parameter
    version already does.  For simplicity, I also made it add any needed outer
    template arguments from the TEMPLATE_DECL to the args.
    
    The testsuite changes are mostly because this change causes unsatisfaction
    to cause deduction to fail rather than reject the candidate later in
    overload resolution.
Comment 2 Jason Merrill 2021-04-01 22:04:08 UTC
(In reply to the_gamester28 from comment #0)
> It seems that the template requirements of invoke_tag(bar_tag, int) are
> considered while evaluating line marked "here". Requirements of irrelevant
> overloads should not be considered, as it can potentially lead to falsely
> reporting a cyclic dependency.

This is as specified by http://wg21.link/cwg2369

I think it would be reasonable to allow a compiler to accept the testcase under a generalization of 13.9.1/9: "If the function selected by overload resolution (12.4) can be determined without instantiating a class template definition, it is unspecified whether that instantiation actually takes place."

But that does not require a compiler to accept it.

It might make sense to check non-dependent conversions that don't require template instantiation, then constraints, then non-dependent conversions that do require template instantiation.  But that's a matter for the committee; G++ is conforming to the current working paper.
Comment 3 gcc-bugs 2021-04-02 12:47:20 UTC
Hi Jason,

as you linked to https://gcc.gnu.org/bugzilla/show_bug.cgi?id=97704, I'm interested if your suggestion would allow that, too?

> But that's a matter for the committee

Will such issues be forwarded, or do we need to submit a paper / regression / idea? I wouldn't know how to proceed here and / or what needs to be done.

Thank you!
Comment 4 Mark M 2021-04-05 19:49:29 UTC
(In reply to Jason Merrill from comment #2)
> (In reply to the_gamester28 from comment #0)
> > It seems that the template requirements of invoke_tag(bar_tag, int) are
> > considered while evaluating line marked "here". Requirements of irrelevant
> > overloads should not be considered, as it can potentially lead to falsely
> > reporting a cyclic dependency.
> 
> This is as specified by http://wg21.link/cwg2369
> 
> I think it would be reasonable to allow a compiler to accept the testcase
> under a generalization of 13.9.1/9: "If the function selected by overload
> resolution (12.4) can be determined without instantiating a class template
> definition, it is unspecified whether that instantiation actually takes
> place."
> 
> But that does not require a compiler to accept it.
> 
> It might make sense to check non-dependent conversions that don't require
> template instantiation, then constraints, then non-dependent conversions
> that do require template instantiation.  But that's a matter for the
> committee; G++ is conforming to the current working paper.

Perhaps I was too quick to assert what I expected the implementation details to be.

The fooable<T> concept should be satisfied for any (T it) for which
invoke_tag(foo_tag{}, it) is unambiguously applicable. The fact that the invoke_tag(bar_tag, T) overload exists should not preclude that. It is not up to me how the compiler comes to that end, but that is the desired outcome.

It is still possible to ban recursive constraints, keep the new GCC 11 implementation, and still have this particular code compile with a small alteration in behaviour:

The compiler does not immediately bail out as soon as it sees recursion in an overload candidate, but marks that particular candidate as invalid.

If there are no applicable overloads (because they are all recursive, or they are invalid for some other reason), then the compiler can reject the code and list all of the reasons why all of the candidates are invalid.

In this case, it would mark invoke_tag(bar_tag, int) as not-applicable due to the constraint recursion, and carry on checking the rest of the overloads for applicability, until it is sure that there is one and only one applicable overload: invoke_tag(foo_tag, int).
Comment 5 Jakub Jelinek 2021-04-27 11:40:33 UTC
GCC 11.1 has been released, retargeting bugs to GCC 11.2.
Comment 6 Patrick Palka 2021-05-01 17:38:01 UTC
FWIW, one workaround for this kind of constraint looping is to encode the non-dependent conversion as a constraint.  For this particular testcase, this would mean changing the signature of

  template<fooable T> auto invoke_tag(bar_tag, T it);

to

  template<std::convertible_to<bar_tag> T, fooable U> auto invoke_tag(T, U it);
Comment 7 Richard Biener 2021-07-28 07:06:07 UTC
GCC 11.2 is being released, retargeting bugs to GCC 11.3
Comment 8 Patrick Palka 2021-08-25 14:44:44 UTC
*** Bug 102065 has been marked as a duplicate of this bug. ***
Comment 9 Richard Biener 2022-04-21 07:48:55 UTC
GCC 11.3 is being released, retargeting bugs to GCC 11.4.
Comment 10 Patrick Palka 2022-08-08 15:06:55 UTC
*** Bug 106538 has been marked as a duplicate of this bug. ***
Comment 11 Patrick Palka 2022-10-27 12:42:06 UTC
*** Bug 107429 has been marked as a duplicate of this bug. ***
Comment 12 catsith 2023-04-04 05:33:04 UTC
*** Bug 109397 has been marked as a duplicate of this bug. ***
Comment 13 Jakub Jelinek 2023-05-29 10:04:19 UTC
GCC 11.4 is being released, retargeting bugs to GCC 11.5.
Comment 14 danakj 2023-06-07 18:25:25 UTC
*** Bug 110160 has been marked as a duplicate of this bug. ***
Comment 15 danakj 2023-06-07 18:41:34 UTC
The workaround listed in Comment #6 does not work for templated types, unfortunately, making Clang and MSVC more expressive here than GCC.

https://godbolt.org/z/obhsqhrbx

```
#include <concepts>
#include <sstream>
#include <string>

#if defined(__GNUC__) && !defined(__clang__)
#define COMPILER_IS_GCC 1
#else
#define COMPILER_IS_GCC 0
#endif

namespace sus::string::__private {
template <class A, class B>
A& format_to_stream(A&, B);

template <class T, class Char>
concept StreamCanReceiveString = requires(T& t, std::basic_string<Char> s) {
    { operator<<(t, s) };
};

/// Consumes the string `s` and streams it to the output stream `os`.
template <class Char, StreamCanReceiveString<Char> S>
S& format_to_stream(S& os, const std::basic_string<Char>& s) {
    os << s;
    return os;
}

}  // namespace sus::string::__private

namespace sus::option {
template <class T>
class Option {};

using namespace ::sus::string::__private;
template <
    class T, 
#if COMPILER_IS_GCC
    std::same_as<Option<T> > Sus_ValueType,  // Does not deduce T.  *****
#endif
    StreamCanReceiveString<char> Sus_StreamType
>
inline Sus_StreamType& operator<<(
    Sus_StreamType& stream,
#if COMPILER_IS_GCC
    const Sus_ValueType& value
#else
    const ::sus::option::Option<T>& value  // Does deduce T.   ****
#endif
) {
    return format_to_stream(stream, std::string());
}

}  // namespace sus::option

int main() {
    std::stringstream s;
    s << sus::option::Option<int>();
}
```
Comment 16 danakj 2023-06-07 20:03:53 UTC
Well for anyone who hits the same issue, it appears that GCC _does_ follow Clang and MSVC in not considering the overload and chasing through the concept resolution if the non-concept types are templates and do not match the caller's arguments.

So you need to do:

1) For non-GCC just use:

  template<fooable T> auto invoke_tag(bar_tag, T it);

2) For GCC non-template type bar_tag use:

  template<std::same_as<bar_tag> T, fooable U> auto invoke_tag(T, U it);

3) For GCC template type bar_tag, back to 1)

  template<class P, fooable T> auto invoke_tag(bar_tag<P>, T it);


Note also that 2) uses same_as, not convertible_to as in Comment #6, otherwise you can get ambiguous overload resolution if multiple types convert to one, which does not occur in Clang/MSVC with the regular type parameter. This _does_ again result in more code that will compile in Clang/MSVC than in GCC, as it prevents conversions from types that don't have an overload.

The macros to do this get rather exciting, if that's of interest to someone in the future: https://github.com/chromium/subspace/pull/253/commits/719500c4d2cbfcfd238d7ee3c5b3d371f40e46c1
Comment 17 Patrick Palka 2023-08-01 18:16:21 UTC
*** Bug 110291 has been marked as a duplicate of this bug. ***
Comment 18 GCC Commits 2023-09-08 16:03:27 UTC
The master branch has been updated by Patrick Palka <ppalka@gcc.gnu.org>:

https://gcc.gnu.org/g:2154bcd6d43cfd821ca70e1583880c4ed955355d

commit r14-3809-g2154bcd6d43cfd821ca70e1583880c4ed955355d
Author: Patrick Palka <ppalka@redhat.com>
Date:   Fri Sep 8 12:02:20 2023 -0400

    c++: refine CWG 2369 satisfaction vs non-dep convs [PR99599]
    
    As described in detail in the PR, the CWG 2369 resolution has the
    surprising consequence of introducing constraint recursion in seemingly
    valid and innocent code.
    
    This patch attempts to fix this surpising behavior for the majority of
    problematic cases.  Rather than checking satisfaction before _all_
    non-dependent conversions, as specified by the CWG resolution, this patch
    makes us first check "safe" non-dependent conversions, then satisfaction,
    then followed by other non-dependent conversions.  A conversion is
    considered "safe" if computing it is guaranteed to not induce template
    instantiation, and we conservatively determine this by checking for
    user-declared constructors (resp. conversion functions) in the parm
    (resp. arg) class type, roughly.
    
            PR c++/99599
    
    gcc/cp/ChangeLog:
    
            * pt.cc (check_non_deducible_conversions): Add bool parameter
            passed down to check_non_deducible_conversion.
            (fn_type_unification): Call check_non_deducible_conversions
            an extra time before satisfaction with noninst_only_p=true.
            (conversion_may_instantiate_p): Define.
            (check_non_deducible_conversion): Add bool parameter controlling
            whether to compute only conversions that are guaranteed to
            not induce template instantiation.
    
    gcc/testsuite/ChangeLog:
    
            * g++.dg/cpp2a/concepts-recursive-sat4.C: Make 'Int' non-aggregate
            in order to preserve intent of the testcase.
            * g++.dg/cpp2a/concepts-nondep4.C: New test.
Comment 19 Patrick Palka 2024-02-02 18:02:33 UTC
The unwanted constraint recursion should be fixed in GCC 14 for the most common cases.
Comment 20 Patrick Palka 2024-03-09 15:37:28 UTC
*** Bug 108393 has been marked as a duplicate of this bug. ***
Comment 21 Jonathan Wakely 2024-03-21 21:40:18 UTC
Here's a very small reproducer:

template<typename T> struct indirect {
  template<typename U> requires // (!is_indirect<U>) &&
    requires (const T& t, const U& u) { t == u; }
  friend constexpr bool operator==(const indirect&, const U&)
  { return false; }

  T* _M_ptr{};
};

indirect<int> i;
bool b = i == 1;


It compiles with trunk, although my real case that this was reduced from doesn't.

It works if you uncomment the extra constraint and define:

template<typename T> class indirect;
template<typename T> constexpr bool is_indirect = false;
template<typename T> constexpr bool is_indirect<indirect<T>> = true;
Comment 22 Jonathan Wakely 2024-03-21 21:43:46 UTC
Here we go, this still fails on trunk, just by making the data member private:

template<typename T> class indirect {
public:
  template<typename U> requires // (!is_indirect<U>) &&
    requires (const T& t, const U& u) { t == u; }
  friend constexpr bool operator==(const indirect&, const U&) { return false; }

private:
  T* _M_ptr{};
};

indirect<int> i;
bool b = i == 1;
Comment 23 Patrick Palka 2024-03-22 14:08:10 UTC
(In reply to Jonathan Wakely from comment #22)
> Here we go, this still fails on trunk, just by making the data member
> private:
That's because for a non-dependent conversion to a class type we only check it before constraints if it's an aggregate class (otherwise it might have a constructor template, which means the conversion might instantiate things making it unsafe to check before constraints).  Maybe we should consider refining the heuristic further.  I believe the code is strictly speaking invalid though.
Comment 24 Jonathan Wakely 2024-03-22 15:58:47 UTC
I have the workaround now, so not urgent for me
Comment 25 Jonathan Wakely 2024-03-27 23:16:08 UTC
It looks like PR 104606 is another case.

#include <optional>
#include <variant>
#include <vector>

struct Value : public std::variant<std::vector<Value>> { };

struct Comparator {
    template <typename T>
    bool operator<=(const T &rhs)
    {
        return true;
    }
};

int main()
{
    auto test2 = Comparator() <= std::optional<Value>{};
}

Preprocessing this with 10.4 and compiling with 11.1 fails, bisection shows it started to fail with r11-2774.
Comment 26 Jonathan Wakely 2024-03-27 23:18:21 UTC
What's bizarre about the PR 104606 case is that this fixes it:

--- a/libstdc++-v3/include/std/optional
+++ b/libstdc++-v3/include/std/optional
@@ -1431,7 +1431,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 #ifdef __cpp_lib_three_way_comparison
   template<typename _Tp, typename _Up>
     requires (!__is_optional_v<_Up>)
-      && three_way_comparable_with<_Tp, _Up>
+      && three_way_comparable_with<_Up, _Tp>
     constexpr compare_three_way_result_t<_Tp, _Up>
     operator<=>(const optional<_Tp>& __x, const _Up& __v)
     { return bool(__x) ? *__x <=> __v : strong_ordering::less; }
Comment 27 Richard Biener 2024-07-19 13:10:51 UTC
GCC 11 branch is being closed.