如何仅在类具有特定方法时才调用特定方法?

How to call a specific method only if class has one?

本文关键字:方法 调用 何仅      更新时间:2023-10-16

我有一个不同类的对象元组。我想遍历元组,并且只有在这些类有一个时调用某个方法。

例如(伪代码):

struct A {  int get( ) { return 5; }; };
struct B { }; 
struct C {  int get( ) { return 10; }; };
int i = 0;
tuple<A, B, C> t;
for ( auto t_element : t ) 
{
if constexpr ( has_get_method( decltype(t_element) ) )
{
i += t_element.get( );
}
}

我已经知道如何遍历元组并检查一个类是否有一些使用 sfinae 的方法,但是如何跳过没有所需方法的对象?

编辑:如果您将来发现这个老问题,请知道现在使用C++20标准的概念可以更轻松地完成此操作。

只需编写一个 sfinae'd 函数和一个包罗万象,以防前一个函数失败。您不必为此使用if constexpr,也不能在 C++14 中实际使用它(这就是您标记问题的方式)。
下面是一个最小的工作示例:

#include <tuple>
#include <iostream>
auto value(...) { return 0; }
template <typename T>
auto value(T &t) -> decltype(t.get()) {
return t.get();
}
struct A { int get() { return 5; }; };
struct B {}; 
struct C { int get() { return 10; }; };
int main() {
int i = 0;
std::tuple<A, B, C> t;
i += value(std::get<0>(t));
i += value(std::get<1>(t));
i += value(std::get<2>(t));
std::cout << i << std::endl;
}

看到它在魔杖盒上启动并运行。

如果您有任何参数要用于测试它,可以使用std::forward

template <typename T, typename... Args>
auto value(T &t, Args&&... args)
-> decltype(t.get(std::forward<Args>(args)...))
{ return t.get(std::forward<Args>(args)...); }

然后调用它为:

i += value(std::get<0>(t), params);

你可以创建一个类型特征,通过声明几个函数来检查一个类是否有get()方法(不需要定义它们)

template <typename>
constexpr std::false_type withGetH (long);
template <typename T>
constexpr auto withGetH (int)
-> decltype( std::declval<T>().get(), std::true_type{} );
template <typename T>
using withGet = decltype( withGetH<T>(0) );

下面是一个完全工作的 c++17 示例

#include <tuple>
#include <iostream>
#include <type_traits>
template <typename>
constexpr std::false_type withGetH (long);
template <typename T>
constexpr auto withGetH (int)
-> decltype( std::declval<T>().get(), std::true_type{} );
template <typename T>
using withGet = decltype( withGetH<T>(0) );
struct A {  int get( ) { return 5; }; };
struct B { }; 
struct C {  int get( ) { return 10; }; };
template <typename T>
int addGet (T & t)
{
int ret { 0 };
if constexpr ( withGet<T>{} )
ret += t.get();
return ret;
}
int main ()
{
int i = 0;
std::tuple<A, B, C> t;
i += addGet(std::get<0>(t));
i += addGet(std::get<1>(t));
i += addGet(std::get<2>(t));
std::cout << i << std::endl;
}

如果不能使用if constexpr,则可以使用标记调度编写(在 c++11/14 中)getAdd()

如下
template <typename T>
int addGet (T & t, std::true_type const &)
{ return t.get(); }
template <typename T>
int addGet (T & t, std::false_type const &)
{ return 0; }
template <typename T>
int addGet (T & t)
{ return addGet(t, withGet<T>{}); }

--编辑--

OP问

我的代码需要检查带有参数的模板化方法。是否可以修改您的解决方案,以便它可以检查类似template<class T, class U> void process(T& t, U& u)的内容,而不是 intget()

我想这是可能的。

您可以创建一个类型特征withProcess2(其中 2 是参数数),该类型特征接收三个模板类型参数:类和两个模板类型

template <typename, typename, typename>
constexpr std::false_type withProcess2H (long);

template <typename T, typename U, typename V>
constexpr auto withProcess2H (int)
-> decltype( std::declval<T>().process(std::declval<U>(),
std::declval<V>()),
std::true_type{} );
template <typename T, typename U, typename V>
using withProcess2 = decltype( withProcess2H<T, U, V>(0) );

下面是一个完全工作的示例(c++17,但现在您知道如何使其成为 c++14),其中包含具有AC和具有 2 个模板参数的process()和具有仅具有 1 个模板参数的process()B

#include <iostream>
#include <type_traits>
template <typename, typename, typename>
constexpr std::false_type withProcess2H (long);

template <typename T, typename U, typename V>
constexpr auto withProcess2H (int)
-> decltype( std::declval<T>().process(std::declval<U>(),
std::declval<V>()),
std::true_type{} );
template <typename T, typename U, typename V>
using withProcess2 = decltype( withProcess2H<T, U, V>(0) );
struct A
{
template <typename T, typename U>
void process(T const &, U const &)
{ std::cout << "A::process(T, U)" << std::endl; }
};
struct B
{
template <typename T>
void process(T const &)
{ std::cout << "B::process(T)" << std::endl; }
};
struct C
{
template <typename T, typename U>
void process(T &, U &)
{ std::cout << "C::process(T, U)" << std::endl; }
};
template <typename T>
void callProcess (T & t)
{
static int  i0 { 0 };
static long l0 { 0L };
if constexpr ( withProcess2<T, int &, long &>{} )
t.process(i0, l0);
}
int main ()
{
std::tuple<A, B, C> t;
callProcess(std::get<0>(t)); // print A::process(T, U)
callProcess(std::get<1>(t)); // no print at all
callProcess(std::get<2>(t)); // print C::process(T, U)
}