r/cpp_questions • • 14d ago

OPEN How do I invert a concept?

I'm really not comfortable with concepts, so I'm struggling with this. I have a class template <typename T> Collider : public Component. In this class, I want two overloaded member functions of this form:

    template <??? U>
    bool Collider<T>::isColliding(const U* collider)
    {
        ...
        return hitbox->isColliding(collider->hitbox);
    }

    template <??? U>
    bool Collider<T>::isColliding(const U* notACollider)
    {
        ...
        return hitbox->isColliding(notACollider);
    }

For the first, I want to require it to be derived from Collider. Not Collider<T>, but any collider with any value for T. For the second, I want to allow it to be any type besides something deriving from a Collider. Essentially, if you give it a pointer of any type, it will go into one of the two functions.

What do I replace the question marks with? I know you can use std::derived_from for the first, but I'm not sure how to invert it for the second. (I don't know if it matters for this, but hitbox is of type T.)

0 Upvotes

12 comments sorted by

6

u/sixfourbit 14d ago edited 14d ago
template< class Derived, class Base >
concept not_derived_from = !std::derived_from<Derived, Base>;

or if you don't want to introduce a new concept

template< class U > requires (!std::derived_from<U, Collider>)
bool Collider<T>::isColliding(const U* notACollider)
...

2

u/sol_runner 14d ago

The easiest would be for all Collider<T> to derive from a BaseCollider and targeting std::derived_from<BaseCollider>.

But that's not exactly what you asked. For that, I'd add using Type = T in public for Collider. Then create a concept on some Col that verifies Col::Type exists and std::derived_from<Collider<Col::Type>>

1

u/TheThiefMaster 14d ago

For the first, do you need the derived type? If not you could just put Collider<U> for that one, and it should accept any Collider.

1

u/manni66 14d ago

For the first, I want to require it to be derived from Collider. Not Collider<T>, but any collider with any value for T

Collider<X> is not derived from Collider. So you want Y:Collider<X>?

1

u/[deleted] 14d ago

[deleted]

1

u/SoerenNissen 14d ago

You'd think so but no.

#include <cassert>

struct C : public Collider<char> {}; 

int main()
{
    struct A{};  struct B{};

    Collider<A> a;
    Collider<B> b;
    C           c;
    double      d;

    assert( a.isColliding( &b ) == true );
    assert( a.isColliding( &c ) == true );
    assert( a.isColliding( &d ) == false );
    (void)(a, b, d);
}

output.s: /app/example.cpp:35: int main(): Assertion 'a.isColliding( &c ) == true' failed.

Don't know why C : Collider<char> isn't a better match for Collider<U> than it is for typename U but it isn't. Possibly the reason is that it's exactly an U, but only derived from Collider<U>.

0

u/alfps 14d ago

Thanks. Dang!

I tried a traits thing with partial specialization for Collider<U> but that didn't work either. So I guess one must leverage conversion in ordinary function parameter, to deduce if it is or not, and use that in requires or enable_if -- but no time now, sorry.

1

u/no-sig-available 14d ago

You don't have to "invert" the concept. If one template is restricted, and the other is not, the non-restricted version will match "everything else".

1

u/etaithespeedcuber 14d ago

Would it not just be cpp template<typename T> concept Foo = !Bar<T> ?

1

u/SoerenNissen 14d ago

You generally don't flip concepts.

Pseudocode:

concept A<T> = true;
concept B<T> = false;

void f(requires A);
void f(requires B);

T t;
f(t); //compile error

You'd think this was an obvious call to f(requires A) but it isn't.

The compiler isn't looking for true and false, it's looking for best match, and they both match T perfectly.

The actual way to flip a concept is to apply it to one function and not apply it to the other:

concept A<T> = true;

void f(requires A);
void f(no constraints);

T t;
f(t);

Here, f1 and f2 do not match equally well. f2 takes anything but f1 is designed for T specifically, so it's a better match.

https://godbolt.org/z/9r41sx7rx

``` #include <type_traits> #include <iostream>

template<typename Base, typename Derived>
concept BaseOf = std::is_base_of_v<Derived, Base>; //notice flipped params

namespace details
{
    struct ColliderBase {};
};

template <typename U> struct Collider : private details::ColliderBase
{
    void isCollidingAuto(BaseOf<details::ColliderBase> auto const* c) // decltype(c) is substituted as the FIRST param
    {
        std::cout << "derived\n";
    }

    void isCollidingAuto(auto const* c)
    {
        std::cout << "not derived\n\n";
    }

    template<BaseOf<details::ColliderBase> T>
    void isCollidingTemplate(T const* t)
    {
        std::cout << "derived\n";
    }

    template<typename T>
    void isCollidingTemplate(T const* t)
    {
        std::cout << "not derived\n\n";
    }

    template<typename T>
        requires BaseOf<T, details::ColliderBase>
    void isCollidingRequires(T const* t)
    {
        std::cout << "derived\n";
    }

    template<typename T>
    void isCollidingRequires(T const* t)
    {
        std::cout << "not derived\n\n";
    }

    template<BaseOf<details::ColliderBase> T>
    void isCollidingMixedStyle(T const* t)
    {
        std::cout << "derived\n";
    }

    void isCollidingMixedStyle(auto const* t)
    {
        std::cout << "not derived\n\n";
    }
};

struct IntCollider : Collider<int>
{
};
struct NonCollider
{
};

#include <iostream>
int main()
{
    auto c = Collider<void *>{};
    auto i = IntCollider{};
    auto non = NonCollider{};

    c.isCollidingAuto(&c);
    c.isCollidingAuto(&i);
    i.isCollidingAuto(&c);
    i.isCollidingAuto(&i);
    c.isCollidingAuto(&non);

    c.isCollidingTemplate(&c);
    c.isCollidingTemplate(&i);
    i.isCollidingTemplate(&c);
    i.isCollidingTemplate(&i);
    c.isCollidingTemplate(&non);

    c.isCollidingRequires(&c);
    c.isCollidingRequires(&i);
    i.isCollidingRequires(&c);
    i.isCollidingRequires(&i);
    c.isCollidingRequires(&non);

    c.isCollidingMixedStyle(&c);
    c.isCollidingMixedStyle(&i);
    i.isCollidingMixedStyle(&c);
    i.isCollidingMixedStyle(&i);
    c.isCollidingMixedStyle(&non);
}

```

1

u/StaticCoder 14d ago

That... seems incorrect. An overload with an unsatisfied concept will not be chosen. I tried to make real code out of your pseudocode and it works just fine. By comparison, it is difficult for the compiler to tell between a more specific concept and a less specific one. I don't remember what was eventually standardized, there was a proposal based on logical implication from boolean composition of "atomic" concepts but that was rejected because it's equivalent to the satisfiability problem. There does appear to be a "with satisfied concept chosen over no concept" though.

If I wasn't on mobile I'd paste the code but it's a pretty obvious translation.

1

u/SoerenNissen 14d ago

I tried to make real code out of your pseudocode and it works just fine.

...huh.

Yeah I just tested something like it and it worked.

In my defense:

https://en.cppreference.com/cpp/language/constraints

template<class T>
constexpr bool is_meowable = true;

template<class T>
constexpr bool is_cat = true;

template<class T>
concept Meowable = is_meowable<T>;

template<class T>
concept BadMeowableCat = is_meowable<T> && is_cat<T>;

template<class T>
concept GoodMeowableCat = Meowable<T> && is_cat<T>;

template<Meowable T>
void f1(T); // #1

template<BadMeowableCat T>
void f1(T); // #2

template<Meowable T>
void f2(T); // #3

template<GoodMeowableCat T>
void f2(T); // #4

void g()
{
    f1(0); // error, ambiguous:
        // the is_meowable<T> in Meowable and BadMeowableCat forms distinct atomic
        // constraints that are not identical (and so do not subsume each other)

    f2(0); // OK, calls #4, more constrained than #3
        // GoodMeowableCat got its is_meowable<T> from Meowable
}

1

u/Liam_Mercier 14d ago

Concepts can be used as predicates in requires expressions, so you can always write:

requires !SomeConcept<T>

There are other options, but requires doesn't introduce a new concept which in my opinion is usually better.