Шаблонные функции c ++ с переменными аргументами - PullRequest
6 голосов
/ 29 июня 2011

Можно ли написать шаблонную функцию c ++, которая принимает переменное количество входных переменных разных типов (количество входных данных может быть ограничено, скажем, 10)? Например, возьмите функцию sql_exec(), которая выполняет строку запроса sql и сохраняет результирующие строки в векторах std указанного типа, т.е.

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 или что-то еще, но мне бы понравился такой простой интерфейс.

Ответы [ 2 ]

6 голосов
/ 29 июня 2011

В C ++ 0x это достигается с помощью шаблонов с переменным числом аргументов (и количество аргументов может быть огромным, ограничение зависит от конкретной реализации).

В C ++ 03 это эмулируется наличием макросов препроцессора, генерирующих партиишаблонных функций различной арности (см. Boost.Preprocessor).

Я использовал технику C ++ 03 для генерации «связывания» от 1 до 10 аргументов, и он работает довольно хорошо.

5 голосов
/ 30 ноября 2011

Как сказано выше, Boost.Preprocessor - это путь, если C ++ 0x недоступен, хотя требуется некоторое время, чтобы привыкнуть к синтаксису.Приведенный ниже пример демонстрирует, как 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(), что более гибко.

...