C++具有可变参数的模板函数

6

是否有可能编写一个C++模板函数,可以接受不同类型的变量作为输入(输入数量可以限制在10个以内)?例如,我们可以考虑一个名为sql_exec()的函数,它执行 SQL 查询并将结果行保存到所提供的标准向量类型中,即

std::vector<double> x,y;
std::vector<std::string> s;
std::string query="select * from ...";

sql_exec(query, s,x,y); // error if less than 3 rows or conversion not possible

我的天真做法是(仅限于最多2个向量)

struct null_type {};
template <typename T1=null_type, typename T2=null_type>
void sql_query(const std::string& query_str, std::vector<T1>& col1,
           std::vector<T2>& col2) {
    ...
}

当然,这很愚蠢,因为我没有告诉函数默认参数,所以我们得到
error: default template arguments may not be used in function templates

但是实际上,它可以使用gcc和-std=c++0x进行编译。然而,很明显sql_query()仍然不能处理可变长度输入,并且需要用2个向量调用。而且,我希望有一个在大多数当前编译器上都能正常工作的可移植性东西。我是否忽略了任何明显的事情?我知道我可以改变设计,也许使用boost::tuple或其他东西,但我希望有这样一个简单的接口。


1
像这样吗?http://en.wikipedia.org/wiki/C%2B%2B0x#Variadic_templates - andrewdski
是的,谢谢。然而,我正在尝试避免使用C++0x,并且递归定义函数的方式在这种情况下会使事情变得困难。由于我对输入数量有限制并且满意,也许还有另一种方法? - tom
几乎可以肯定地说,有一种优雅的方法可以使用可变参数模板。这种基于模板的思维需要一点时间来适应,但它可能比你没有可变参数模板时编写的任何东西都要简单得多。 - Kerrek SB
2个回答

6
在C++0x中,这是通过可变参数模板实现的(参数数量可以很大,限制取决于具体实现)。
在C++03中,这是通过使用预处理器宏生成各种形参的模板函数来模拟的(请参见Boost.Preprocessor)。
我已经使用C++03技术生成了从1到10个参数的“bind”,而且它运行得非常好。

5

如上所述,如果C++0x不可用,则Boost.Preprocessor是正确的选择,尽管需要一些时间来适应其语法。下面的示例演示了如何使用Boost.Preprocessor来定义具有可变(但有限)参数数量的函数。

#include <boost/preprocessor/repetition.hpp>
#include <boost/preprocessor/iteration/local.hpp>
#include <boost/preprocessor/iteration/iterate.hpp>

#define MAX_PARAMS  2

class sql {
public:
   // definition of the function in macro form
   #define SQL_QUERY_DEF(z, n, unused)                                     \
   template <BOOST_PP_ENUM_PARAMS(n, class T)>                             \
   void query(const std::string& query,                                    \
            BOOST_PP_ENUM_BINARY_PARAMS(n, const T, & x) );

   // does the actual code replication of SQL_QUERY_DEF
   #define BOOST_PP_LOCAL_MACRO(n)  SQL_QUERY_DEF(~, n, ~)
   #define BOOST_PP_LOCAL_LIMITS    (1, MAX_PARAMS)
   #include BOOST_PP_LOCAL_ITERATE()

   ...
};


// two helper functions:
// expands to var0.clear(); var1.clear(); ...
#define SQL_VECTOR_CLEAR(z,i,var) var##i.clear();
// expands to var0.push_back(this->get_col<T0>(0); ...
#define SQL_VECTOR_PUSH_BACK(z,i,var) var##i.push_back(this->get_col<T##i>(i));

// definition of the function in macro form
#define SQL_QUERY(z, n, unused)                                               \
template <BOOST_PP_ENUM_PARAMS(n, class T)>                                   \
void sql::query(const std::string& query,                                     \
                  BOOST_PP_ENUM_BINARY_PARAMS(n, std::vector< T,>& x) ){      \
   this->do_query(query);                                                     \
   if(this->num_cols()<n){                                                    \
      throw std::runtime_error();                                             \
   }                                                                          \
   BOOST_PP_REPEAT(n, SQL_VECTOR_CLEAR, x)                                    \
   while(this->is_open()) {                                                   \
      BOOST_PP_REPEAT(n, SQL_VECTOR_PUSH_BACK, x)                             \
      this->step();                                                           \
   }                                                                          \
}

// does the actual code replication of SQL_QUERY
#define BOOST_PP_LOCAL_MACRO(n)  SQL_QUERY(~, n, ~)
#define BOOST_PP_LOCAL_LIMITS    (1,  MAX_PARAMS)
#include BOOST_PP_LOCAL_ITERATE()

预处理器将此扩展为:
$ g++ -P -E sql.cpp | astyle

class sql {
public:
   template < class T0> void query(const std::string& query, const T0 & x0 );
   template < class T0 , class T1> void query(const std::string& query, const T0 & x0 , const T1 & x1 );
   ...
};
template < class T0> void sql::query(const std::string& query, std::vector< T0 >& x0 ) {
   this->do_query(query);
   if(this->num_cols()<1) {
      throw std::runtime_error();
   }
   x0.clear();
   while(this->is_open()) {
      x0.push_back(this->get_col<T0>(0));
      this->step();
   }
}
template < class T0 , class T1> void sql::query(const std::string& query, std::vector< T0 >& x0 , std::vector< T1 >& x1 ) {
   this->do_query(query);
   if(this->num_cols()<2) {
      throw std::runtime_error();
   }
   x0.clear();
   x1.clear();
   while(this->is_open()) {
      x0.push_back(this->get_col<T0>(0));
      x1.push_back(this->get_col<T1>(1));
      this->step();
   }
}

注意,这里我们不能使用BOOST_PP_REPEAT(MAX_PARAMS, SQL_QUERY, ~),因为它从0个参数开始复制,但我们需要从1个参数开始,这就是为什么需要BOOST_PP_LOCAL_ITERATE(),它更加灵活。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接