检查元组中的项目是否包含特定方法

Check if an item inside a tuple contains a specific method

我有一个元组,我正在尝试为元组中的每个对象调用 initialize()。现在,某些对象可能不包含该功能。我知道我可以通过使用 C++20 requires 功能检查 class 是否包含给定函数来完成这项工作。不幸的是,我正在使用 MSVC,它还不支持。

可以用不同的方式写吗?我可以使用 C++20,但它应该在 MSVC

上编译
template <class... Args>
struct CustomTuple : std::tuple<Args...> {
  // Calls "initialize() for each item inside the tuple if that function exists"
  template <std::size_t I = 0>
  auto initialize() {
       std::decay_t<decltype(std::get<I>(*this))> item;
       constexpr bool has_initialize = requires() { // doesnt't work on MSVC
           item.initialize(); 
       };
       if constexpr (has_initialize) {
           std::get<I>(*this).initialize();
       }
       if constexpr (I + 1 != sizeof...(Args)) {
           initialize<I + 1>();
    }
  }
};

您可以定义自己的概念。

template<typename T>
concept has_initialize = requires(T a) {
    a.initialize();
};

template <class... Args>
struct CustomTuple : std::tuple<Args...> {
  // Calls "initialize() for each item inside the tuple if that function exists"
  template <std::size_t I = 0>
  auto initialize() {
       std::decay_t<decltype(std::get<I>(*this))> item;
       if constexpr (has_initialize<decltype(item)>) {
           std::get<I>(*this).initialize();
       }
       if constexpr (I + 1 != sizeof...(Args)) {
           initialize<I + 1>();
    }
  }
};

https://godbolt.org/z/rvhPPsqcz

如果您还没有访问 requires 的权限,那么您将不得不使用 SFINAE 并编写一个类型特征来检测 initialize() 是否是一个可调用的成员函数。

这可以用 std::void_t 来完成,尽管有点笨拙:

#include <type_traits> // std::void_t
#include <utility>     // std::declval

template <typename T, typename = void>
struct has_initialize_impl : std::false_type {};

template <typename T>
struct has_initialize_impl<T, 
  std::void_t<decltype(std::declval<T&>().initialize())>
> : std::true_type {};

template <typename T>
inline constexpr auto has_initialize = has_initialize_impl<T>::value;

以上特征检查是否有任何可变引用 T& 可以调用名为 initialize() 的函数。如果表达式的格式正确,则 has_initialized 的计算结果为 true;否则,它的计算结果为 false.

这可以与您的其余代码一起使用,只需稍加改动:

template <class... Args>
struct CustomTuple : std::tuple<Args...> {
  // Calls "initialize() for each item inside the tuple if that function exists"
  template <std::size_t I = 0>
  auto initialize() {
       // Note: no need to construct a temporary/unused 'item' type,
       // we can just use 'tuple_element' here to get the type
       using element_type = typename std::tuple_element<I, std::tuple<Args...>>::type;
       
       if constexpr (has_initialize<element_type >) {
           std::get<I>(*this).initialize();
       }
       if constexpr (I + 1 != sizeof...(Args)) {
           initialize<I + 1>();
    }
  }
};

Live Example


如果您认为您可能有更多需要检测功能的情况,但仍然没有 C++20 requires 支持,那么可能值得考虑添加“检测到的” "-idiom + 样板到您的代码中,可以在 cppreference.

上找到

这与 std::void_t 的行为相同,但如果经常需要则更方便。这仍然比 c++20 的 requires 更迟钝,但如果您的编译器不支持它,您没有太多选择。

使用 is_detected_v,您只需为需要验证的任何表达式定义模板类型别名,然后将其简单地传递给特征即可。

例如上面的解可以简明地解为:

template <typename T>
using detect_initialize = decltype(std::declval<T&>().initialize());

...

if constexpr (is_detected_v<detect_initialize, decltype(item)>) {
    ...
}

Live Example

使用 C++17 功能并将问题拆分为更小的函数:

template<typename T, typename U = void>
struct has_inistialize_t : std::false_type {};

template<typename T>
struct has_inistialize_t<T, std::void_t<decltype(
std::declval<T>().initialize())>> : std::true_type {};

template<typename T>
void tryInitialize_helper(T&& x, std::true_type)
{
    std::forward<T>(x).initialize();
}

template<typename T>
void tryInitialize_helper(T&& x, std::false_type)
{}

template<typename T>
void initialize_if_possible(T&& x)
{
    tryInitialize_helper(std::forward<T>(x), has_inistialize_t<T>{});
}

template<typename ...Ts>
void initilize_each_if_possible(std::tuple<Ts...>& t)
{
    std::apply([](auto&... args){(initialize_if_possible(args), ...);}, t);
}

https://godbolt.org/z/vcEnjY7vE

SFINAE 的另一种方式(并且没有递归)

struct low_priority {};
struct high_priority : low_priority {};

template <typename T>
auto initialize_impl(T& t, high_priority) -> decltype(t.initialize())
{ t.initialize(); }

template <typename T>
void initialize_impl(T&, low_priority) { /*Empty*/ }



template <class... Args>
struct CustomTuple : std::tuple<Args...> {
  // Calls "initialize() for each item inside the tuple if that function exists"
  void initialize() {
       std::apply([](auto&... args)
           {
               (initialize_impl(args, high_priority{}), ...);
           },
           *this);
  }
};

注意:优先级可以用 C++20 中的适当概念代替。