【问题标题】:Variadic Template simulating "runtime" expansion模拟“运行时”扩展的可变参数模板
【发布时间】:2015-03-21 06:38:08
【问题描述】:

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

这是旧代码:

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 和指针来保留它?

【问题讨论】:

  • 顺便说一句,旧代码具有未定义的行为。您对va_arg 的调用不能保证以任何特定顺序发生——函数参数可以以任何顺序进行评估。我见过编译器出于各种异想天开的原因对它们进行重新排序,例如更改优化设置后。

标签: c++ templates variadic-templates


【解决方案1】:

您可以添加虚拟函数,例如:

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 以在没有额外内容的情况下产生错误)。

【讨论】:

  • 非常有趣!我需要了解您所写的内容。使用 sizeof 查看传递了多少参数,但如果函数具有相同数量的参数,则它将失败。这是您使用 std::is_convertable 所指的吗?但是那么如何确定第 n 个参数的 is_convertable 呢?
  • @user3072517:刚刚添加了代码来修复您的命令函数之间可能存在的不兼容原型。
  • 我向o'模板绝地大师鞠躬!惊人的!绝对棒极了。我已根据您的输入对其进行了微调,现在在未正确使用时会显示编译器消息。
【解决方案2】:

非常感谢 @Jarod42 提供的出色解决方案。我在这里做了一些调整,以在编译过程中(通过编译指示消息)在模板使用不正确时警告用户。它并不完美,但至少它给出了一些执行错误的迹象。在 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{});
}

【讨论】:

  • 老鼠!编译指示警告是个好主意,但是一旦我开始真正添加函数,就会生成警告,即使它实际上并没有被使用。嗯……是个好主意。
猜你喜欢
  • 2014-04-12
  • 2014-10-30
  • 2013-10-03
  • 1970-01-01
  • 2015-05-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多