【问题标题】:Calling different C functions according to the C++ template type根据C++模板类型调用不同的C函数
【发布时间】:2013-04-11 22:16:18
【问题描述】:

我的问题如下: 我有一个 C 库,其中包含每个函数的多个版本,具体取决于它们使用的数据类型,例如:

void add(double *a, double *b, double *c);

void sadd(float *a, float *b, float *c);

现在,有了一个外部 C++ 模板函数,我希望能够执行以下操作:

template<class T>
void myfunc(/*params*/)
{
 // Obtain a,b,c of type T* from params

/*
 If T is double call add(a,b,c);
 else if T is float call sadd(a,b,c).
*/ 
}

我知道它可以通过专门的模板函数来完成,例如:

template<>
void myfunc<double>(/*params*/)
{
  // Obtain a,b,c of type double* from params
  add(a,b,c);
}

等等,但这并不是一个真正的选择,因为引入模板化 C++ 函数的全部目的是减少代码重复,并且“// 从参数中获取 T* 类型的 a、b、c”部分可以是真的很长。

这个问题有简单的解决方案吗?

谢谢

兹德内克

【问题讨论】:

  • 你可以使用多态函数
  • 简短的回答是否定的。如果代码都是 C++,那么您为 double *float * 参数重载了 add,这会起作用,但是对于每个使用单独名称的“手动重载”,您还必须生成调用每个单独的代码。
  • 这里模板的意义不仅仅是“减少代码”。这里最重要的部分是类型推导。
  • 或者你可以使用函数指针(有缺点)。但是您必须在代码中的某处列出(重复)C 函数的名称。
  • 是的,这就是我所害怕的。我想我可以为 C 库做简单的 C++ 模板包装器。

标签: c++ c templates macros


【解决方案1】:

定义重载的 C++ 转发器:

inline void forward_add(double *a, double *b, double *c) { add( a, b, c ); }
inline void forward_add(float *a, float *b, float *c) { sadd( a, b, c ); }

template<class T>
void myfunc(/*params*/)
{
   // Obtain a,b,c of type T* from params
   forward_add( a, b, c );
}

【讨论】:

  • 可能值得将const 引用传递给模板化函数。
  • @CaptainObvlious 在 OPs C 函数的情况下不是。可能还有其他情况应该考虑这一点,但在这里不会有任何区别。
  • @DanielFrey 真的。如果他需要处理右值,他可以重载myfunc 并按照他的快乐方式行事。
  • @ZdenekPrusa myfunc 只知道forward_add,您只是将sadd 的可见性限制为单线转发器。您必须在某处指定sadd。或者,也许我不完全理解您的要求... :-/
【解决方案2】:

您必须在某个地方告诉编译器 saddadd 是相关的。

一种方法是特征类 模板 结构数学;

template<>
struct math<double> {
  static void add(double *a, double *b, double *c) {
    return ::add(a, b, c);
  }
};
template<>
struct math<float> {
  static void add(float*a, float*b, float*c) {
    return ::sadd(a, b, c);
  }
};

你在哪里使用它:

template<class T>
void myfunc(/*params*/)
{
  // Obtain a,b,c of type T* from params

  math<T>::add( a, b, c );
}

这具有将所有基于类型的重构放在一个位置的优点和缺点。

另一种方法是创建具有 doublefloat 重载的独立 C++ 函数。这样做的优点和缺点是允许您的代码分布在多个位置。

void math_add( double* a, double* b, double* c ) {
  add(a,b,c);
}
void math_add( float* a, float* b, float* c ) {
  sadd(a,b,c);
}

现在,假设您的所有函数都使用相同的名称模式——foo 对应于doublesfoo 对应于float。在这种情况下,可以使用基于文本的代码生成来缓解上述一些“编写重载”代码。

这里唯一的问题是函数的签名可以变化。如果只有少数几个,简单的宏就可以了:

#define MAKE_FUNCS( f ) \
  void CONCAT( math_, f ) ( double* a, double* b, double * c ) { \
    f ( a, b, c ); \
  } \
  void CONCAT( math_, f ) ( float* a, float* b, float* c ) { \
    CONCAT( f, s ) ( a, b, c ); \
  }

然后,只需为您尝试以这种方式克隆的库中的每个函数发送MAKE_FUNCS 垃圾邮件。

一个缺点(在许多缺点中)是它只支持一组固定的签名。我们可以通过完美转发来解决这个问题,这是一种 C++11 技术:

#define MAKE_FUNCS( f ) \
  template< typename... Args >\
  auto f ( Args&&... args ) \
    -> decltype(::f ( std::forward<Args>(args)... )) \
  { \
    ::f ( std::forward<Args>(args)... ); \
  } \
  template< typename... Args >\
  auto f ( Args&&... args ) \
    -> decltype(:: CONCAT( f, s ) ( std::forward<Args>(args)... )) \
  { \
    :: CONCAT( f, s ) ( std::forward<Args>(args)... ); \
  }

但这会遇到 SFINAE 和相同的签名问题。您可以通过显式表达式 SFINAE 来解决此问题:

#include <utility>
#include <type_traits>
#include <cstddef>
#include <iostream>

#define CONCAT2( a, b ) a##b
#define CONCAT( a, b ) CONCAT2(a,b)

// SFINAE helper boilerplate:
template<typename T> struct is_type:std::true_type {};
template<std::size_t n> struct secret_enum { enum class type {}; };
template<bool b, std::size_t n>
using EnableIf = typename std::enable_if< b, typename secret_enum<n>::type >::type;

// Macro that takes a srcF name and a dstF name and an integer N and
// forwards arguments matching dstF's signature.  An integer N must be
// passed in with a distinct value for each srcF of the same name:
#define FORWARD_FUNC( srcF, dstF, N ) \
template< typename... Args, \
  EnableIf< is_type< \
    decltype( dstF ( std::forward<Args>(std::declval<Args>())... )) \
  >::value , N >... > \
auto srcF ( Args&&... args ) \
  -> decltype(dstF ( std::forward<Args>(args)... )) \
{ \
  dstF ( std::forward<Args>(args)... ); \
}

#define MAKE_FUNCS( f ) \
  FORWARD_FUNC( f, ::f, 0 ) \
  FORWARD_FUNC( f, :: CONCAT( f, s ), 1 )

void add( double* a, double* b, double* c) {*a = *b+*c;}
void adds( float* a, float* b, float* c) {*a = *b+*c;}
namespace math {
  MAKE_FUNCS(add)
}
int main() {
  double a, b = 2, c = 3;
  float af, bf = 3, cf = 5;
  math::add( &a, &b, &c );
  math::add( &af, &bf, &cf );
  std::cout << a << "=" << b << "+" << c << "\n";
  std::cout << af << "=" << bf << "+" << cf << "\n";
}

但是正如您所看到的,这变得相当迟钝,并且目前没有多少编译器可以处理这种级别的 C++11 主义。 (我认为以上内容应该在 gcc 4.8 和 intel 的最新版本中编译,但不是 MSVC 或 clang 3.2)

现在您只需获取库中的每个函数,并创建一个包含一堆单行样板的头文件:

namespace mymath {
  MAKE_FUNCS( add )
  MAKE_FUNCS( sub )
  MAKE_FUNCS( chicken )
}
#undef MAKE_FUNCS

然后您可以通过说 mymath::add 而不是 addadds 来调用它。

这也可以通过其他形式的文本代码生成来完成。

【讨论】:

  • 在你的第一个MAKE_FUNCS宏中,第二个函数不应该有float参数吗?
  • @ZdenekPrusa 是的,有点乱:创建 C++11 完美转发 SFINAE 函数的宏。我会避免将它作为一种解决方案,仅仅是因为没有人能弄清楚它在做什么......唯一的好处是,毕竟是骇客,使用骇客的代码看起来很漂亮。 :)
  • @ZdenekPrusa 使模板 spew 更易于理解,并发表了评论!
  • @Yakk 你测试你的代码了吗?至少有一个错字true_tupe 以及在模板参数中使用函数参数的一些奇怪之处。编辑:第二个模板参数根本不是模板参数o.O
  • @DyP 测试代码?那是什么蠢事。我只是在没有编译器的情况下从头开始编写它,这会造成什么危害?...好的,好的,经过测试,tpyos 和错误已修复。荒谬(tm)版本现在builds and runs.
猜你喜欢
  • 2019-04-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多