嵌套的 std::conditional_t 失败了,但使用类专业化技巧好吗?为什么?[复制]

Nested std::conditional_t failed but using class specialization tricks OK? Why? [duplicate]

提问人:Alexander Chen 提问时间:9/22/2023 最后编辑:Jan SchultkeAlexander Chen 更新时间:9/22/2023 访问量:75

问:

我有一个特征类来调用一些可调用对象。它可以接受参数与否。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

编译器资源管理器演示

C++ 模板 C++17 类型特征

评论


答:

4赞 康桓瑋 9/22/2023 #1

对于 ,无论条件是否满足,都将始终实例化两个相应的类型。因此,当其中一个类型无效时,将发生硬错误。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) {

评论

0赞 Weijun Zhou 9/22/2023
为什么在这里需要?type_identity::type
1赞 康桓瑋 9/22/2023
@WeijunZhou 推导的类型可能不是默认构造的。invoke_result_t
1赞 Jan Schultke 9/22/2023 #2

问题是缺乏短路。 如果调用不可行,则不会包含 ,并且您始终通过 .std::invoke_result::typestd::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;

查看 Compiler Explorer 中的实时示例

std::conditional_t现在用于选择特征类型,而不是特征类型内。 特征类型仅在最后访问一次,这避免了早期失败。::type::type


注意:std::enable_if<true> 只是定义特征的一种方式,其中 ::type = void。如果您使用的是 C++20,则 std::type_identity<void> 会更合适。