【问题标题】:How to call a specific method only if class has one?仅当类有一个特定方法时如何调用?
【发布时间】:2017-09-16 21:03:38
【问题描述】:

我有一组不同类的对象。我想遍历元组并仅在那些类有某个方法时才调用某个方法。

例如(伪代码):

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 标准中的概念可以更轻松地完成此操作。

【问题讨论】:

  • if constexpr id c++17;你标记了 c++14;你是说c++17吗?
  • 无论如何你都不能使用 ranged-for 来迭代元组,所以即使你让 SFINAE 让成员函数存在工作,迭代仍然不会按照你想要的方式工作。
  • 您可以获得支持调用“成员函数”的类型的索引,然后对每个索引使用元组上的递归 std::get&lt;I&gt;
  • std::apply([&amp;](auto&amp;&amp;... elements) { ([&amp;](auto&amp;&amp; element) { /* if constexpr here */ }(std::forward&lt;decltype(elements)&gt;(elements)), ...) }, t); 进行迭代,但这是一个不同的问题。

标签: c++ templates c++14 template-meta-programming c++17


【解决方案1】:

只需编写一个 sfinae'd 函数和一个 catchall 以防前一个失败。您不必为此使用 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;
}

wandbox 上查看并运行它。

如果你有任何参数想要用来测试它,你可以使用std::forward as:

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);

【讨论】:

  • 感谢您的回答。它对我有用,除了我的代码需要检查带有参数的模板化方法。是否可以修改您的解决方案,以便它可以检查 template&lt;class T, class U&gt; void process(T&amp; t, U&amp; u) 之类的东西,而不是 int get()
  • @RinatVeliakhmedov 你知道要传入什么参数吗?
  • @skypjak 不确定我是否理解正确,但是是的,我从元组代码中知道这些参数的类型。
【解决方案2】:

您可以创建一个类型特征,通过声明几个函数(无需定义它们)来检查一个类是否具有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&lt;class T, class U&gt; void process(T&amp; t, U&amp; u) 之类的东西,而不是 int get()

我想这是可能的。

您可以创建接收三个模板类型参数的类型特征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,带有 process(),带有 2 个模板参数和 B process() 只有 1 个模板参数。

#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)
 }

【讨论】:

  • 感谢您的回答。它对我有用,除了我的代码需要检查带有参数的模板化方法。是否可以修改您的解决方案,以便它可以检查 template&lt;class T, class U&gt; void process(T&amp; t, U&amp; u) 之类的东西,而不是 int get()
  • @RinatVeliakhmedov - 是的,我认为这是可能的;答案得到改善。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-08
  • 1970-01-01
  • 2023-03-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多