5

Czy można napisać funkcję szablonu C++, która pobiera zmienną liczbę zmiennych wejściowych różnych typów (liczba wejść może być ograniczona do 10)? Na przykład wziąć funkcję sql_exec() które wykonuje ciąg kwerendy SQL i zapisuje wynikowe wiersze w wektorach std typu dostarczanego tjC++ funkcje szablonu ze zmiennymi argumentami

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 

Teraz moje naiwne podejście byłoby (ograniczone do maksymalnie 2 nosicieli)

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) { 
    ... 
} 

oczywiście, że jest głupi jak nie powiedzieć o funkcji domyślnych argumentów i mamy

error: default template arguments may not be used in function templates 

ale rzeczywisty ly to kompiluje się z gcc i -std=c++0x. Jednak, oczywiście, sql_query() nadal nie przyjmuje wejścia o zmiennej długości i musi zostać wywołane za pomocą 2 wektorów. Ponadto chciałbym mieć coś przenośnego działającego na większości obecnych kompilatorów. Czy coś oczywistego przeoczyłem? Wiem, że mogę zmienić projekt i może użyć boost::tuple lub czegoś innego, ale wolałbym taki prosty interfejs.

+1

Podoba Ci się to? http://pl.wikipedia.org/wiki/C%2B%2B0x#Variadic_templates – andrewdski

+0

Tak, dziękuję. Jednak staram się uniknąć C++ 0x, a także rekursywny sposób definiowania funkcji utrudniłby w tym przypadku. Ponieważ jestem zadowolony z ograniczonej maksymalnej liczby wejść, może jest inny sposób? – tom

+0

jest prawie na pewno sposób na eleganckie korzystanie z szablonów variadic. Tego rodzaju myślenie oparte na szablonach wymaga trochę przyzwyczajenia się do tego, ale prawdopodobnie będzie znacznie prostsze niż wszystko, co można stworzyć bez szablonów variadycznych. –

Odpowiedz

4

Jak wspomniano powyżej, Boost.Preprocessor jest sposobem, aby przejść, jeśli C++ 0x nie jest dostępny, chociaż przyzwyczajenie się do składni zajmuje trochę czasu. Poniższy przykład demonstruje sposób, w jaki Boost.Preprocessor może być używany do definiowania funkcji ze zmienną (ale ograniczoną) liczbą argumentów.

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

Preprocesor rozszerza to:

$ 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(); 
    } 
} 

Uwaga, tutaj nie możemy używać BOOST_PP_REPEAT(MAX_PARAMS, SQL_QUERY, ~) gdyż rozpoczyna replikację z 0 parametrami ale musimy zacząć 1, dlatego BOOST_PP_LOCAL_ITERATE() jest potrzebna, które jest bardziej elastyczny.

6

W C++ 0x to osiągnięto poprzez szablony variadic (a liczba argumentów może być ogromna, a limit jest konkretną implementacją).

W języku C++ 03 jest to emulowane poprzez posiadanie makr preprocesora generujących wiele funkcji szablonów o różnej arytmetyce (patrz Boost.Preprocessor).

Użyłem techniki C++ 03 do wygenerowania "wiązania" od 1 do 10 argumentów i działa całkiem nieźle.

Powiązane problemy