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



我有一个元组,我正试图为元组中的每个对象调用initialize((。现在,有些对象可能不包含该函数。我知道我可以通过使用C++20需要功能来检查类是否包含给定的函数来实现这一点。不幸的是,我正在使用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>();
}
}
};

如果您还没有访问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>();
}
}
};

实时示例


如果你认为你可能有更多的情况需要检测功能,但仍然没有C++20requires支持,那么添加";检测到"-习语+样板文件到您的代码中,可以在cpprreference上找到。

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

使用is_detected_v,您只需要定义需要验证的任何表达式的模板类型别名,并简单地将其传递给trait。

例如,上述解决方案可以简洁地解决为:

template <typename T>
using detect_initialize = decltype(std::declval<T&>().initialize());
...
if constexpr (is_detected_v<detect_initialize, decltype(item)>) {
...
}

实时示例

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中用适当的概念代替。

您可以定义自己的概念。

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

使用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

最新更新