Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Call a function that is specifically not templated

I have a bunch of functions that check for collisions between various types of shape.

bool Collides(Rect r, Circle c);
bool Collides(Rect r, Line l);
bool Collides(Line l, Circle c);

I was hoping I could implement a single templated function that could halve my implementation count by allowing it to swap the input parameters. So that instead of also having to implement:

// The same as before but the input parameters swapped
bool Collides(Circle c, Rect r) { return Collides(r, c); }
bool Collides(Line l, Rect r) { return Collides(r, l); }
bool Collides(Circle c, Line l) { return Collides(l, c); }

I could instead write this once:

template <typename Shape1, typename Shape2>
bool Collides(Shape1 a, Shape2 b)
{
    return Collides(b, a);
}

Unfortunately when both Collides(a, b) an Collides(b, a) are unimplemented it calls the templated function recursively at runtime, which is obviously unintended behaviour.

Is there some C++ tag or feature that allows you to turn off or disallow argument type deduction for a specified line or block? The intent would be to force the compiler to look for a non-templated implementation then fail to compile if one didn't exist.

like image 869
Troyseph Avatar asked Dec 31 '22 03:12

Troyseph


2 Answers

One time that a function template is not looked up is during the declaration of the function (Before the opening {). Taking advantage of that, we can SFINAE unimplemented arguments out:

template<typename Shape1, typename Shape2>
auto Collides(Shape1 a, Shape2 b) -> decltype(::Collides(b, a)) {
    return Collides(b, a);
}

Though note that this must be written after all the other declarations of Collides.


You can also just call a different function that delegates:

template<typename Shape1, typename Shape2>
auto ActualCollides(Shape1 a, Shape2 b) -> decltype(Collides(a, b)) {
    return Collides(a, b);
}

template<typename Shape1, typename Shape2>
auto ActualCollides(Shape1 a, Shape2 b) -> decltype(Collides(b, a)) {
    return Collides(b, a);
}

// Or rename `Collides` into `CollidesImpl` and you can call this `Collides` instead

Which will take into account future Collides functions because of ADL.

like image 57
Artyer Avatar answered Jan 09 '23 20:01

Artyer


There's no way to remove function templates from the overload set. In your particular case there are workarounds, such as:

struct CollidesImpl {
    bool operator()(Rect r, Circle c);
    bool operator()(Rect r, Line l);
    bool operator()(Line l, Circle c);
};

template <typename Shape1, typename Shape2>
bool Collides(Shape1 a, Shape2 b)
{
    static_assert(std::is_invocable_v<CollidesImpl, Shape1, Shape2> ||
                  std::is_invocable_v<CollidesImpl, Shape2, Shape1>,
                  "No implementation exists for these argument types");
    if constexpr(std::is_invocable_v<CollidesImpl, Shape1, Shape2>) {
        return CollidesImpl{}(a, b);
    } else {
        return CollidesImpl{}(b, a);
    }
}
like image 33
Brian Bi Avatar answered Jan 09 '23 18:01

Brian Bi