Skip to content

Concept short circuit doesn't work if requires clause is after function parameters when checking concepts #55945

@andoalon

Description

@andoalon

The compiler behaves differently depending on whether the same requires clause for a function template is written between the template parameters and the function signature or after the function signature. In the former case it performs short-circuit evaluation (and agrees with gcc) whereas in the latter case it doesn't, leading to infinite recursion in my case.

However, short-circuit doesn't misbehave in all cases, since I didn't manage to hit the issue in a simpler case I tested (see below my case), so it doesn't seem to be related only to short-circuit evaluation, but concept evaluation as well.

The issue involves a struct Thing with a constructor template with a single argument, constrained by std::copy_constructible<T>, that could possibly be a better match than the copy constructor (when T = Thing). I expected that changing the contraint to !std::is_same_v<T, Thing> && std::copy_constructible<T> would solve the issue, but that worked or not depending on the location in which I wrote the requires clause.

Possibly related to #44304

My case (reproduction): Compiler Explorer

#include <concepts>
#include <type_traits>

struct Thing
{
    Thing() = default;
    Thing(const Thing&) = default;

#ifdef REQUIRES_AFTER_TEMPLATE
    // Works (expected)
    template <typename T>
    requires (!std::is_same_v<T, Thing> && std::copy_constructible<T>)
    Thing(const T& /*t*/)
#else
    // Does NOT work (UNEXPECTED)
    // Conjunction short-circuit does not
    // seem to happen correctly:
    // https://en.cppreference.com/w/cpp/language/constraints#Conjunctions
    template <typename T>
    Thing(const T& /*t*/)
    requires (!std::is_same_v<T, Thing> && std::copy_constructible<T>)
#endif
    {}
};


void test(int a)
{
    auto thing = Thing(a);

    // This one breaks, when checking if
    // `Thing` models `std::copy_constructible`, because
    // the compiler tries to check
    // if the possibly-copy-constructor is a valid
    // copy constructor, which leads to checking
    // its constraints, therefore checking `std::copy_constructible` again,
    // which leads to infinite recursion
    [[maybe_unused]] auto thing_of_thing = Thing(thing);
}

Simple case, no issues: Compiler Explorer

#include <concepts>
#include <type_traits>

template <typename T>
struct Empty
{};

template <typename T>
constexpr bool breaks_if_instantiated = Empty<T>::value;

struct Thing
{
    Thing() = default;
    
#ifdef REQUIRES_AFTER_TEMPLATE
    // Works (expected)
    template <typename T>
    requires (!std::is_same_v<T, Thing> && breaks_if_instantiated<T>)
    Thing(const T& /*t*/)
#else
    // Works (expected)
    template <typename T>
    Thing(const T& /*t*/)
    requires (!std::is_same_v<T, Thing> && breaks_if_instantiated<T>)
#endif
    {}
};


void test(int a)
{
    // Breaks (expected)
    //auto thing_with_int = Thing(a);

    Thing thing{};

    [[maybe_unused]] auto thing_with_thing = Thing(thing);
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    clang:frontendLanguage frontend issues, e.g. anything involving "Sema"conceptsC++20 concepts

    Type

    No type

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions