Detect whether operator exists and callable in c++ (considering static_asserts)
Asked Answered
C

1

6

Given 2 types T and U I want to detect whether it's possible to call operator * between those to objects (i.e is it possible to write t * u where t is of type T and u is of type U)

I'm using c++ detection idiom but since it's not yet available in my compiler I implemented it myself like this

struct nonesuch {
    nonesuch() = delete;
    ~nonesuch() = delete;
    nonesuch(nonesuch const&) = delete;
    void operator=(nonesuch const&) = delete;
};

namespace detail {
template <class Default, class AlwaysVoid, template<class...> class Op, class... Args>
struct detector {
    using value_t = std::false_type;
    using type = Default;
};

template <class Default, template<class...> class Op, class... Args>
struct detector<Default, std::void_t<Op<Args...>>, Op, Args...> {
    using value_t = std::true_type;
    using type = Op<Args...>;
};

} // namespace detail

template <template<class...> class Op, class... Args>
using is_detected = typename detail::detector<nonesuch, void, Op, Args...>::value_t;

template< template<class...> class Op, class... Args >
constexpr bool is_detected_v = is_detected<Op, Args...>::value;

Now I have such helper:

template <typename T, typename U>
using multiply = decltype(std::declval<T>() * std::declval<U>());

and to detect whether it's callable I call

bool can_multiply = is_detected_v<multiply, T, U>

It's almost fine , for example this prints 1, 0 as expected

std::cout << is_detected_v<multiply, int, int> << std::endl;
std::cout << is_detected_v<multiply, std::vector<int>, std::vector<int>> << std::endl;

But now I have class

template<typename T>
class A {
};

template<typename T>
A<T> operator*(const A<T>&, const A<T>&) {
    static_assert(!std::is_same<bool, T>::value);
    return A<T>();
}

here A<bool> can't be multiplied by A<bool> but my code detects that it's possible

std::cout << is_detected_v<multiply, A<bool>, A<bool>> << std::endl; // 1
A<bool>() * A<bool>(); // does't compile

So, my question is, how to fix my code to not detect methods when they static_asserted out? I suppose I can replace static_assert with some sfinae but I don't want to (because I don't have access and besides static_asserts have better error messages).

Colorblind answered 11/9, 2016 at 17:6 Comment(0)
K
7

So, my question is, how to fix my code to not detect methods when they static_asserted out?

You simply can't. That's just one of the downsides of static_assert - there is no way to externally verify the validity of an operation. That's because static_assert doesn't happen in the "immediate context" of the instantiation of operator*, and so SFINAE doesn't apply - it will always be a hard error.

I suppose I can replace static_assert with some sfinae but I don't want to (because I don't have access and besides static_asserts have better error messages).

I sympathize. But that's basically the trade-off. SFINAE and type checking, or static_assert and clearer errors. (Of course in this case you could just write a non-template A<bool> operator*(A<bool> const&, A<bool> const&) but that's probably besides the point).

Krummhorn answered 11/9, 2016 at 17:10 Comment(1)
Explaining why might help (SFINAE checks must be done during overload resolution; forcing every compiler to compile every function and determine if it has any illegal code prior to determining which overload to call would both be hard for compiler writers, and could result in surprising behavior (I preferred void* over T* because your T* code contains a bug leading to an error in code 2 levels nested deep)). SFINAE checks are a compromise, and even there MSVC falls down hard on them.Farrell

© 2022 - 2024 — McMap. All rights reserved.