2015-03-20 2 views
3

У меня есть функция, которую я пытаюсь преобразовать, чтобы использовать вариативные шаблоны. К сожалению, расширение шаблона вызывает проблемы при попытке строго ввести функции во время компиляции.Шаблон Variadic, имитирующий расширение «runtime»

Вот старый код:

std::unique_ptr<std::stringstream> Execute(CommandType command, ...) { 
    auto resp = std::make_unique<std::stringstream>(); 

    va_list vl; 
    va_start(vl, command); 

    switch(command) { 
    case CommandType::Post: 
     *resp << Post(va_arg(vl, char *), va_arg(vl, char *)); 
     break; 
    case CommandType::Get: 
     *resp << Get(va_arg(vl, char *)); 
     break; 
    case CommandType::Delete: 
     *resp << Delete(va_arg(vl, char *), va_arg(vl, char *)); 
     break; 
    } 
    va_end(vl); 
    return resp; 
} 

и соответствующие функции:

bool Post(char *command, char *payload); 
char *Get(char *command); 
bool Delete(char *command, char *name); 

В идеале, я хотел бы быть в состоянии преобразовать это что-то вдоль линий этого:

template< typename... Params> 
std::unique_ptr<stringstream> Execute(CommandType command, Params... parameters) { 
    auto response = std::make_unique<stringstream>(); 
    if(command == CommandType::Get) 
     response << Get(parameters); 
    else if(command == CommandType::Post) 
     response << Post(parameters); 
    else if(command == CommandType::Delete) 
     response << Delete(parameters); 
    else if(command == CommandType::OtherFunc) 
     response << OtherFunc(parameters); 

    return response; 
}; 

bool Post(std::string command, std::string payload); 
std:string Get(std::string command); 
bool Delete(std::string command, std::string name); 
int OtherFunc(std::string command, bool enabled, MyClass name); 
  • OtherFunc добавлен здесь для получения дополнительной информации Пример типа lex.

Но, очевидно, это не работает, потому что компилятор считает, что каждая команда должна получить параметры, переданные в шаблон, когда только один на основе CommandType должен фактически получать параметры.

Любые трюки, чтобы переписать это с помощью шаблонов и поддерживать сильные типы, или мне нужно оставить это, используя переменные arg и указатели?

+1

Как в стороне, старый код имеет неопределенное поведение. Ваши вызовы 'va_arg' не гарантируются в любом конкретном порядке - аргументы функции могут быть оценены в любом порядке. Я видел, как компиляторы переупорядочивают их по разным причинам, например. после изменения настроек оптимизации. – StilesCrisis

ответ

2

Вы можете добавить фиктивные функции, что-то вроде:

template<typename ... Ts> 
typename std::enable_if<sizeof...(Ts) != 2, int>::type 
Post (Ts&&...) {return 0;} 

template<typename ... Ts> 
typename std::enable_if<sizeof...(Ts) != 2, int>::type 
Delete (Ts&&...) {return 0;} 

template<typename ... Ts> 
typename std::enable_if<sizeof...(Ts) != 3, int>::type 
OtherFunc (Ts&&...) {return 0;} 

SFINAE фактически более сложный (и должны использовать std::is_convertible), цель состоит в том, чтобы избежать использования функции шаблона, когда вы не используете точные типы, но конвертируемые типы.

Live example

Чтобы быть более полным, лишняя версия с std::is_convertible

template<typename T> 
typename std::enable_if<!std::is_convertible<T, std::string>::value, int>::type 
Get (T&&...) {return 0;} 

template<typename T1, typename T2> 
typename std::enable_if<!std::is_convertible<T1, std::string>::value 
    || !std::is_convertible<T2, std::string>::value, 
    int>::type 
Post (T1&&, T2&&) {return 0;} 

template<typename T1, typename T2> 
typename std::enable_if<!std::is_convertible<T1, std::string>::value 
    || !std::is_convertible<T2, std::string>::value, 
    int>::type 
Delete (T1&&, T2&&) {return 0;} 

Live example (Обратите внимание, что я изменил OtherFunc производить ошибки без дополнительного материала).

+1

Очень интересно! Мне нужно понять что-то на том, что вы написали. Используя sizeof, чтобы посмотреть, сколько параметров передано, но если функции имеют одинаковое количество параметров, то это не сработает. Это то, о чем вы говорите, используя std :: is_convertable? Но тогда как вы определяете is_convertable по n-му параметру? – user3072517

+0

@ user3072517: добавлен код для исправления возможного несовместимого прототипа между вашими командами. – Jarod42

+0

Я склоняюсь к шаблону мастера джедая! Потрясающие! Абсолютно потрясающе. Я немного изменил его на основе вашего ввода, и теперь он показывает сообщения компилятора, когда они не используются должным образом. – user3072517

0

Большое спасибо @ Jarod42 за выдающееся решение. Я сделал несколько настроек, чтобы предупредить пользователя во время компиляции (через сообщения pragma), когда шаблон используется неправильно. Это не идеально, но, по крайней мере, он дает некоторые указания на то, что выполнение будет неправильным. И в VS2013 вы можете дважды щелкнуть предупреждающее сообщение, и оно приведет вас к строке ошибки. К сожалению, где он определен, а не там, где он используется, но, по крайней мере, это нечто, а не предупреждение вообще.

Вот живой пример: http://ideone.com/qJpYUQ

Примечание: Обратите внимание на код ниже .... Прагма сообщения в WIN32 версии работает в VS2013, но я не уверен, что GCC. Он компилируется, но я не вижу предупреждающих сообщений на Ideone.com. Поэтому может потребоваться несколько настроек, если я не получу прагму для gcc.

Снова спасибо @ Jarod42 !!

#include <cassert> 
#include <memory> 
#include <sstream> 
#include <string> 
#include <iostream> 

class MyClass {}; 

bool Post(std::string /*command*/, std::string /*payload*/) { std::cout << "Post\n"; return false;} 
std::string Get(std::string /*command*/) { std::cout << "Get\n"; return ""; } 
bool Delete(std::string /*command*/, std::string /*name*/) { std::cout << "Delete\n"; return false;} 
int OtherFunc(std::string /*command*/, const MyClass& /*name*/) { std::cout << "OtherFunc\n"; return 0;} 

enum class CommandType 
{ 
    Get, Post, Delete, OtherFunc 
}; 

#define Stringify(T) #T 
#define MakeString(M, L) M(L) 
#define $Line MakeString(Stringify, __LINE__) 
#ifdef WIN32 
#define TemplateErrMsg __FILE__ "(" $Line ") : Invalid template used:" __FUNCTION__ 
#define INVALID_TEMPLATE { __pragma(message(TemplateErrMsg)); assert(false && TemplateErrMsg); return 0; } 
#else 
#define TemplateErrMsg __FILE__ "(" $Line ") : Invalid template used:" MakeString(Stringify, __FUNCTION__) 
#define DO_PRAGMA(x) _Pragma (#x) 
#define INVALID_TEMPLATE {DO_PRAGMA(message(TemplateErrMsg)); assert(false && TemplateErrMsg); return 
#endif 

template<typename ... Ts> 
typename std::enable_if<sizeof...(Ts) != 1, int>::type 
Get (Ts&&...) INVALID_TEMPLATE 

template<typename T> 
typename std::enable_if<!std::is_convertible<T, std::string>::value, int>::type 
Get (T&&...) INVALID_TEMPLATE 

template<typename ... Ts> 
typename std::enable_if<sizeof...(Ts) != 2, int>::type 
Post (Ts&&...) INVALID_TEMPLATE 

template<typename T1, typename T2> 
typename std::enable_if<!std::is_convertible<T1, std::string>::value 
    || !std::is_convertible<T2, std::string>::value, 
    int>::type 
Post (T1&&, T2&&) INVALID_TEMPLATE 

template<typename ... Ts> 
typename std::enable_if<sizeof...(Ts) != 2, int>::type 
Delete (Ts&&...) INVALID_TEMPLATE 

template<typename T1, typename T2> 
typename std::enable_if<!std::is_convertible<T1, std::string>::value 
    || !std::is_convertible<T2, std::string>::value, 
    int>::type 
Delete (T1&&, T2&&) INVALID_TEMPLATE 

template<typename ... Ts> 
typename std::enable_if<sizeof...(Ts) != 2, int>::type 
OtherFunc (Ts&&...) INVALID_TEMPLATE 

template<typename T1, typename T2> 
typename std::enable_if<!std::is_convertible<T1, std::string>::value 
    || !std::is_convertible<T2, const MyClass&>::value, 
    int>::type 
OtherFunc (T1&&, T2&&) INVALID_TEMPLATE 

template<typename... Ts> 
std::unique_ptr<std::stringstream> 
Execute(CommandType command, Ts&&... parameters) { 
    auto response = std::make_unique<std::stringstream>(); 
    if(command == CommandType::Get) 
     *response << Get(std::forward<Ts>(parameters)...); 
    else if(command == CommandType::Post) 
     *response << Post(std::forward<Ts>(parameters)...); 
    else if(command == CommandType::Delete) 
     *response << Delete(std::forward<Ts>(parameters)...); 
    else if(command == CommandType::OtherFunc) 
     *response << OtherFunc(std::forward<Ts>(parameters)...); 

    return response; 
} 


int main(){ 
    Execute(CommandType::Get, "hello"); 
    Execute(CommandType::Post, "hello", "world"); 
    Execute(CommandType::Delete, "hello", "world"); 
    Execute(CommandType::OtherFunc , 123, "test", MyClass{}); 
} 
+0

Крысы! Предупреждения прагмы были хорошей идеей, но как только я начал добавлять функции в реальность, были созданы предупреждения, даже если они фактически не использовались. Ах, хорошо ... была хорошая мысль. – user3072517

Смежные вопросы