Search code examples
c++c++20c++-concepts

C++20 concept that supposedly depends on itself


In my world, a StrictNodeType should be anything that defines the types PredContainer and SuccContainer, so I wrote

  template<typename N>
  concept StrictNodeType = requires {
    typename N::PredContainer;
    typename N::SuccContainer;
  };

However, GCC-11.2 gives me the following error:

error: satisfaction of atomic constraint 'requires{typename N::PredContainer;typename N::SuccContainer;} [with N = typename std::remove_cvref<_Tp>::type::Node]' depends on itself
  164 |   concept StrictNodeType = requires {
      |                            ^~~~~~~~~~
  165 |     typename N::PredContainer;
      |     ~~~~~~~~~~~~~~~~~~~~~~~~~~
  166 |     typename N::SuccContainer;something;
      |     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  167 | };

How is that possible? Why can't GCC simply inspect the given type and check if it provides the requested subtypes?

Here's a minimum breaking example:

#include <type_traits>

template<typename N>
concept StrictNodeType = requires {
    typename N::something;
};

template<StrictNodeType N> using Int = int; 

template<int>
struct X {using type = Int<X>; };

using ThisBreaks = Int<X<0>>;

Solution

  • It seems there is an infinite recursion issue that constrains a concept more than it would be without it. I made a few changes to get more directly at the issue:

    #include <type_traits>
    
    template<typename N>
    concept StrictNodeType = requires {
        typename N::something;
    };
    
    #if 1
    template<StrictNodeType N> using Int = int; 
    #else
    template<typename N> using Int = int; 
    #endif
    
    template<int>
    struct X { using something = Int<X<0>>; };
    
    using ThisBreaks=Int<X<0>>;
    
    ThisBreaks foo()
    {
        return ThisBreaks{};
    }
    

    This yields the following error:

    <source>:15:37: error: template constraint failure for 'template<class N>  requires  StrictNodeType<N> using Int = int'
       15 | struct X { using something = Int<X<0>>; };
          |                                     ^~
    <source>:15:37: note: constraints not satisfied
    <source>: In substitution of 'template<class N>  requires  StrictNodeType<N> using Int = int [with N = X<0>]':
    <source>:15:37:   required from here
    <source>:4:9:   required for the satisfaction of 'StrictNodeType<N>' [with N = X<0>]
    <source>:4:26:   in requirements  [with N = X<0>]
    <source>:5:14: note: the required type 'typename N::something' is invalid
        5 |     typename N::something;
          |     ~~~~~~~~~^~~~~~~~~~~~~
    cc1plus: note: set '-fconcepts-diagnostics-depth=' to at least 2 for more detail
    <source>:17:25: error: template constraint failure for 'template<class N>  requires  StrictNodeType<N> using Int = int'
       17 | using ThisBreaks=Int<X<0>>;
          |                         ^~
    <source>:17:25: note: constraints not satisfied
    <source>: In substitution of 'template<class N>  requires  StrictNodeType<N> using Int = int [with N = X<0>]':
    <source>:17:25:   required from here
    <source>:4:9:   required for the satisfaction of 'StrictNodeType<N>' [with N = X<0>]
    <source>:4:26:   in requirements  [with N = X<0>]
    <source>:5:14: note: the required type 'typename N::something' is invalid
        5 |     typename N::something;
          |     ~~~~~~~~~^~~~~~~~~~~~~
    <source>:19:1: error: 'ThisBreaks' does not name a type
       19 | ThisBreaks foo()
          | ^~~~~~~~~~
    Compiler returned: 1
    

    Changing #if 1 to #if 0 compiles fine, only the Concept objects to the infinite recursion.

    (Play with it here: https://godbolt.org/z/56Yd7W3sf )