I have an if constexpr
checking that a type is equality-comparable with itself. I use std::is_invocable_v<std::equal_to<>, T, T>
.
However, when T
is a vector of incomparable structs, the snippet falsely returns True. Is there a deep reason for it or is it a compiler bug?
Minimal example follows.
#include <type_traits>
#include <iostream>
#include <vector>
class TNonComparable{};
int main()
{
std::cout << std::is_invocable_v<std::equal_to<>, TNonComparable, TNonComparable> << "\n";
// 0
std::cout << std::is_invocable_v<
std::equal_to<>,
std::vector<TNonComparable>,
std::vector<TNonComparable>
> << "\n";
// 1
std::vector<TNonComparable> vec;
// vec == vec;
// (expected) compilation error
}
I checked the output at Godbolt's, it is the same for all recent versions of g++ and clang.
I think it's very important to look at what std::is_invocable
does:
Determines whether
Fn
can be invoked with the argumentsArgTypes...
. Formally, determines whetherINVOKE(declval<Fn>(), declval<ArgTypes>()...)
is well formed when treated as an unevaluated operand, whereINVOKE
is the operation defined inCallable
.
Emphasis mine.
The important part to note here is that std::equal_to<>
used inside std::is_invocable
will never be evaluated because it's a unevaluated operand. This means that it only checks if operator==
exists at all, which it does for std::vector<>
, not if it would compile in an evaluated context.
I think, this is correct behavior.
In the first std::is_invokable_v
checks for the presence of the operator==
in the TNonComparable
type. It is not present - so the result is 0.
In the second case std::is_invokable_v
checks for the equality operator of the std::vector
, which is present and may be invoked. But if do try to invoke it, it won't be able to compile because the TNonComparable
type doesn't have operator==
. But before you don't try to use it, it won't generate an error.
Maybe, in second case you should check for value_type of the std::vector:
std::cout << std::is_invocable_v<
std::equal_to<>,
std::vector<TNonComparable>::value_type,
std::vector<TNonComparable>::value_type
> << "\n";
// 0
Had some time and I've wrote workaround tool
namespace detail
{
template<typename T, typename=void>
struct has_value_type : std::false_type {};
template<typename T>
struct has_value_type<T, std::void_t<typename T::value_type>> : std::true_type {};
template<typename T, typename=void>
struct is_pair : std::false_type {};
template<typename T, typename U>
struct is_pair<std::pair<T, U>, void> : std::true_type {};
template<typename T, typename=void>
struct is_tuple : std::false_type {};
template<typename ...Args>
struct is_tuple<std::tuple<Args...>, void> : std::true_type {};
}
template<typename T, typename=void>
struct has_euqual_operator : std::false_type {};
template<typename T>
struct has_euqual_operator<
T,
std::void_t<
std::enable_if_t<
!detail::is_pair<T>::value
&& !detail::has_value_type<T>::value
&& !detail::is_tuple<T>::value>,
decltype(std::declval<T>() == std::declval<T>())
>
> : std::true_type
{};
template<typename T>
struct has_euqual_operator<
T,
std::enable_if_t<has_euqual_operator<typename T::value_type>::value>
> : std::true_type
{};
template<typename T>
struct has_euqual_operator<
T,
std::enable_if_t<has_euqual_operator<typename T::first_type>::value
&& has_euqual_operator<typename T::second_type>::value
>
> : std::true_type
{};
template<typename ...Args>
struct has_euqual_operator<
std::tuple<Args...>,
std::enable_if_t<(... && has_euqual_operator<Args>::value)>
> : std::true_type
{};
https://godbolt.org/z/rcejhx
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With