11

Here is the simple example:

#include <type_traits>
#include <ranges>
#include <vector>

struct MyClass
{
    void f( int ) {}
    void f( char ) {}

    template <std::ranges::input_range Rng>
        requires requires ( MyClass cls, const std::ranges::range_value_t<Rng>& val )
        {
            { cls.f( val ) };
        }
    void f( Rng&& rng ) {}
};

int main()
{
    MyClass cls;
    cls.f( 10 );
    cls.f( 'a' );
    cls.f( std::vector<int>{ 10, 15 } );
}

According to Godbolt this example compiles successfully on MSVC and GCC, but fails to compile on Clang. What does the standard say about such require-expressions?

Update: we might simplify the example so that there is no circularity in methods calls (Godbolt):

#include <type_traits>
#include <ranges>
#include <vector>

struct MyClass
{
    void g( int ) {}
    void g( char ) {}

    template <std::ranges::input_range Rng>
        requires requires ( MyClass cls, const std::ranges::range_value_t<Rng>& val )
        {
            { cls.g( val ) };
        }
    void f( Rng&& rng ) {}
};

int main()
{
    MyClass cls;
    cls.g( 10 );
    cls.g( 'a' );
    cls.f( std::vector<int>{ 10, 15 } );
}

The compilers act the same way as for the original example.

Andrew
  • 311
  • 1
  • 7
  • 1
    I would agree with clang about incomplete type `MyClass`... – Jarod42 Aug 02 '23 at 14:20
  • And with "evil" range with value_type as itself, `f` would exist if.. `f` exists... ^_^ – Jarod42 Aug 02 '23 at 14:22
  • @Jarod42 I agree about an "evil" range, but it would generate circular dependency of concepts and lead to compilation failure, not runtime infinite loop. As for the original problem, the question is where require-clauses are checked. I think they should be checked on instantiation, and during the instantiation `MyClass` is a complete type. – Andrew Aug 02 '23 at 14:43
  • [Reduced](https://godbolt.org/z/coccjhY98) with no header. – 康桓瑋 Aug 02 '23 at 14:46
  • @康桓瑋 I think that's too reduced - now the constraint isn't actually dependent, so I think the answer to that question might be different. In OP, the constraint is still dependent, so I'm pretty sure it should be valid? – Barry Aug 02 '23 at 16:01
  • 1
    @Jarod42 An evil range like... `std::filesystem::path`? – Barry Aug 02 '23 at 16:01

1 Answers1

1

[temp.inst]/17 arguably supports at least the non-circularity example, at the very least if MyClass is a class template [emphasis mine]:

The type-constraints and requires-clause of a template specialization or member function are not instantiated along with the specialization or function itself, even for a member function of a local class; substitution into the atomic constraints formed from them is instead performed as specified in [temp.constr.decl] and [temp.constr.atomic] when determining whether the constraints are satisfied or as specified in [temp.constr.decl] when comparing declarations.

As, for a class template, [temp.inst]/11 holds:

An implementation shall not implicitly instantiate a function template, a variable template, a member template, a non-virtual member function, a member class or static data member of a templated class, or a substatement of a constexpr if statement ([stmt.if]), unless such instantiation is required.

Clang accepts both examples if MyClass is made into a class template (circularity DEMO, non-circularity DEMO).

[temp.inst]/17 could be argued to apply also for a non-template classes given its statement about local classes, as local classes are not allowed to be class templates, meaning Clang rejects-invalid at least the non-circularity example.

dfrib
  • 70,367
  • 12
  • 127
  • 192