提问人:Alexander Chen 提问时间:9/22/2023 最后编辑:Jan SchultkeAlexander Chen 更新时间:9/22/2023 访问量:75
嵌套的 std::conditional_t 失败了,但使用类专业化技巧好吗?为什么?[复制]
Nested std::conditional_t failed but using class specialization tricks OK? Why? [duplicate]
问:
我有一个特征类来调用一些可调用对象。它可以接受参数与否。range
我的代码如下。
#include <functional>
#include <type_traits>
template <typename PrototypeClassType, typename... Args>
struct InvokeResultTraits {
static constexpr bool is_invocable_with_range_v =
std::is_invocable_v<PrototypeClassType, int, Args...>;
static constexpr bool is_invocable_without_range_v =
std::is_invocable_v<PrototypeClassType, Args...>;
using invoke_result_t = std::conditional_t<
is_invocable_with_range_v,
std::invoke_result_t<PrototypeClassType, int, Args...>,
std::conditional_t<is_invocable_without_range_v,
std::invoke_result_t<PrototypeClassType, Args...>,
void>>;
static inline auto Invoke(const PrototypeClassType &p, int range,
Args &&...args) -> invoke_result_t {
if constexpr (is_invocable_with_range_v) {
return std::invoke(p, range, std::forward<Args>(args)...);
} else if constexpr (is_invocable_without_range_v) {
return std::invoke(p, std::forward<Args>(args)...);
} else {
static_assert(!sizeof(PrototypeClassType),
"No suitable operator() const");
}
}
};
// Use traits to perform a call
template <typename Prototype, typename... Args>
auto bar(const Prototype &p, int range, Args &&...args) {
return InvokeResultTraits<Prototype, Args...>::Invoke(
p, range, std::forward<Args>(args)...);
}
但是,将导致编译器错误std::conditional_t
error: no type named 'type' in 'struct std::invoke_result<Foo1, bool>' void>>
你怎么能解释这个错误?
作为一种解决方法,我使用了一个类专业化技巧来做同样的事情,这很有效。
template <bool CanInvokeWithInt, bool CanInvokeWithoutInt,
typename PrototypeClassType, typename... Args>
struct InvokeResultType {
using type = void;
};
template <typename PrototypeClassType, typename... Args>
struct InvokeResultType<true, false, PrototypeClassType, Args...> {
using type = std::invoke_result_t<PrototypeClassType, int, Args...>;
};
template <typename PrototypeClassType, typename... Args>
struct InvokeResultType<false, true, PrototypeClassType, Args...> {
using type = std::invoke_result_t<PrototypeClassType, Args...>;
};
template <typename PrototypeClassType, typename... Args>
struct InvokeResultTraits {
// same as above ...
using invoke_result_t =
typename InvokeResultType<is_invocable_with_range_v,
is_invocable_without_range_v,
PrototypeClassType, Args...>::type;
// same as above ...
};
有人可能会注意到这是无用的。但帮助程序类也能够获取结果类型。InvokeResultTraits::invoke_result_t
答:
对于 ,无论条件是否满足,都将始终实例化两个相应的类型。因此,当其中一个类型无效时,将发生硬错误。conditional_t
这与类专用化不同,类专用化只会实例化满足条件的专用化。
一种解决方法是通过调用包含不同编译时分支条件的 lambda 来确定结果类型
using invoke_result_t = decltype([] {
if constexpr (is_invocable_with_range_v)
return std::invoke_result<PrototypeClassType, int, Args...>{};
else if constexpr (is_invocable_without_range_v)
return std::invoke_result<PrototypeClassType, Args...>{};
else
return std::type_identity<void>{};
}())::type;
或者你可以只声明返回类型 as ,这样编译器就可以自动推断出返回类型并删除冗余类型别名。Invoke()
decltype(auto)
invoke_result_t
static inline auto Invoke(const PrototypeClassType &p, int range,
Args &&...args) -> decltype(auto) {
评论
type_identity
::type
invoke_result_t
问题是缺乏短路。 如果调用不可行,则不会包含 ,并且您始终通过 .std::invoke_result
::type
std::invoke_result_t
您可以通过一些小的调整使第一个解决方案起作用:
using invoke_result_t = typename std::conditional_t<
is_invocable_with_range_v,
std::invoke_result<PrototypeClassType, int, Args...>,
std::conditional_t<is_invocable_without_range_v,
std::invoke_result<PrototypeClassType, Args...>,
std::enable_if<true>>>::type;
std::conditional_t
现在用于选择特征类型,而不是特征类型内。
特征类型仅在最后访问一次,这避免了早期失败。::type
::type
注意:std::enable_if<true>
只是定义特征的一种方式,其中 ::type = void
。如果您使用的是 C++20,则 std::type_identity<void>
会更合适。
评论