可变模板模拟“运行时”扩展

时间:2015-03-20 05:11:22

标签: c++ templates variadic-templates

我有一个函数,我试图将其转换为使用可变参数模板。不幸的是,在编译期间尝试强类型化函数时,模板扩展会导致问题。

这是旧代码:

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,用于更复杂的类型示例。

但显然这不起作用,因为当只有一个基于CommandType的实际接收参数时,编译器认为每个命令都应该将参数传递给模板。

使用模板重写这个并保持强类型的任何技巧,或者我是否必须使用var args和指针保留它?

2 个答案:

答案 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),目的是避免在不使用确切类型但可转换类型时使用模板函数。

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 :(得分: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{});
}