std::is_invocable 对模板类型的意外结果
Unexpected result of std::is_invocable over a template type
我有一个 if constexpr
检查类型是否与自身相等。我用 std::is_invocable_v<std::equal_to<>, T, T>
.
然而,当 T
是一个无与伦比的结构向量时,该片段错误地 returns 正确。是有深层原因还是编译器错误?
下面是最简单的例子。
#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
}
我检查了 Godbolt 的输出,所有最新版本的 g++ 和 clang 都一样。
我认为,这是正确的行为。
在第一个 std::is_invokable_v
中检查 TNonComparable
类型中是否存在 operator==
。它不存在 - 因此结果为 0。
在第二种情况下 std::is_invokable_v
检查 std::vector
的相等运算符,它存在并且可以被调用。但是如果尝试调用它,它将无法编译,因为 TNonComparable
类型没有 operator==
。但在您不尝试使用它之前,它不会产生错误。
也许,在第二种情况下,您应该检查 std::vector:
的 value_type
std::cout << std::is_invocable_v<
std::equal_to<>,
std::vector<TNonComparable>::value_type,
std::vector<TNonComparable>::value_type
> << "\n";
// 0
我认为 look at std::is_invocable
的作用非常重要:
Determines whether Fn
can be invoked with the arguments ArgTypes...
.
Formally, determines whether INVOKE(declval<Fn>(), declval<ArgTypes>()...)
is well formed when treated as an unevaluated
operand, where INVOKE
is the operation defined in Callable
.
强调我的。
这里要注意的重要部分是 std::is_invocable
中使用的 std::equal_to<>
永远不会被计算,因为它是一个未计算的操作数。这意味着它只检查 operator==
是否存在,它为 std::vector<>
做的,而不是它是否会在评估的上下文中编译。
有一些时间,我写了解决方法工具
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
{};
我有一个 if constexpr
检查类型是否与自身相等。我用 std::is_invocable_v<std::equal_to<>, T, T>
.
然而,当 T
是一个无与伦比的结构向量时,该片段错误地 returns 正确。是有深层原因还是编译器错误?
下面是最简单的例子。
#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
}
我检查了 Godbolt 的输出,所有最新版本的 g++ 和 clang 都一样。
我认为,这是正确的行为。
在第一个 std::is_invokable_v
中检查 TNonComparable
类型中是否存在 operator==
。它不存在 - 因此结果为 0。
在第二种情况下 std::is_invokable_v
检查 std::vector
的相等运算符,它存在并且可以被调用。但是如果尝试调用它,它将无法编译,因为 TNonComparable
类型没有 operator==
。但在您不尝试使用它之前,它不会产生错误。
也许,在第二种情况下,您应该检查 std::vector:
的 value_typestd::cout << std::is_invocable_v<
std::equal_to<>,
std::vector<TNonComparable>::value_type,
std::vector<TNonComparable>::value_type
> << "\n";
// 0
我认为 look at std::is_invocable
的作用非常重要:
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
.
强调我的。
这里要注意的重要部分是 std::is_invocable
中使用的 std::equal_to<>
永远不会被计算,因为它是一个未计算的操作数。这意味着它只检查 operator==
是否存在,它为 std::vector<>
做的,而不是它是否会在评估的上下文中编译。
有一些时间,我写了解决方法工具
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
{};