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

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

Closed
andoalon opened this issue Jun 8, 2022 · 3 comments
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" concepts C++20 concepts

Comments

@andoalon
Copy link

andoalon commented Jun 8, 2022

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);
}
@EugeneZelenko EugeneZelenko added clang:frontend Language frontend issues, e.g. anything involving "Sema" concepts C++20 concepts and removed new issue labels Jun 8, 2022
@llvmbot
Copy link
Collaborator

llvmbot commented Jun 8, 2022

@llvm/issue-subscribers-clang-frontend

@CaseyCarter
Copy link
Member

I think this is a minimal repro (https://godbolt.org/z/be95bsh8E):

template <class, class> constexpr bool is_same_v = false;
template <class T> constexpr bool is_same_v<T, T> = true;

template <class>
constexpr bool always_false = false;

template <class T, class U>
concept different_from = !is_same_v<T, U>;

template <class T>
constexpr bool do_not_instantiate() {
    static_assert(always_false<T>);
	return true;
}

template <class T>
concept do_not_check = do_not_instantiate<T>();

template <different_from<int> T>
void f(T t) requires do_not_check<T> {}
inline void f(int) {}

int main() {
    f(42);
}

This program is well-formed since !is_same_v<int, int> is false, which should inhibit substitution into do_not_check<T> when checking the constraints on the function template f.

@usx95
Copy link
Contributor

usx95 commented Oct 28, 2022

Fixed at trunk.

@usx95 usx95 closed this as completed Oct 28, 2022
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
Status: Done
Development

No branches or pull requests

5 participants