我有一个函数,我试图将其转换为使用可变参数模板。不幸的是,在编译期间尝试强类型化函数时,模板扩展会导致问题。
这是旧代码:
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);
但显然这不起作用,因为当只有一个基于CommandType的实际接收参数时,编译器认为每个命令都应该将参数传递给模板。
使用模板重写这个并保持强类型的任何技巧,或者我是否必须使用var args和指针保留它?
答案 0 :(得分: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
),目的是避免在不使用确切类型但可转换类型时使用模板函数。
更完整,使用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 :(得分:0)
非常感谢@ Jarod42提供出色的解决方案。我在这里做了一些调整,以便在编译时(通过编译指示消息)在错误地使用模板时警告用户。它并不完美,但至少它给出了一些迹象表明执行是错误的。在VS2013中,您可以双击警告消息,它将转到错误行。不幸的是,它定义的地方,而不是它被使用的地方,但至少它是一些而不是没有任何警告。
以下是实例:http://ideone.com/qJpYUQ
注意:注意以下代码.... WIN32版本中的pragma消息在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{});
}