3

I have a function that I am trying to convert it to use variadic templates. Unfortunately, the template expansion causes problems when attempting to strongly type the functions during compile time.

Here is the old code:

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;
}

and the corresponding functions:

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

Ideally, I would like to be able to convert this to something along the lines of this:

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 added here for more complex type example.

But obviously this doesn't work because the compiler thinks that each command should get the parameters passed into the template when only one based on the CommandType should actually receive the parameters.

Any tricks to rewrite this using templates and maintain strong types, or do I have to leave this using var args and pointers?

user3072517
  • 513
  • 1
  • 7
  • 21
  • 1
    As an aside, the old code has undefined behavior. Your invocations of `va_arg` aren't guaranteed to happen in any particular order--function arguments can be evaluated in any order. I've seen compilers reorder them for all sorts of whimsical reasons, e.g. after changing optimization settings. – StilesCrisis Mar 20 '15 at 23:22

2 Answers2

2

You may add dummy functions, something like:

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 is in fact more complicated (and should use std::is_convertible), the goal is to avoid to use the template function when you don't use the exact types but convertible types.

Live example

To be more complete, the extra version with 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 (Note that I changed OtherFunc to produce errors without the extra stuff).

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • 1
    Very interesting! I need to understand something on what you wrote. Using sizeof to look at how many parameters are passed but if the functions have the same number of parameters, then it will fail. Is this what you are referring to by using std::is_convertable? But then how do you determine is_convertable on the nth parameter? – user3072517 Mar 20 '15 at 16:36
  • @user3072517: just added code to fix possible incompatible prototype between your command functions. – Jarod42 Mar 20 '15 at 18:34
  • I bow to the o' template jedi master! Awesome! Absolutely awesome. I have tweaked it slightly based on your input and it now shows compiler messages when not used properly. – user3072517 Mar 20 '15 at 22:27
0

Many thanks to @Jarod42 for an outstanding solution. I made a few tweaks here to warn the user during compilation (via pragma messages) when a template is used incorrectly. It is not perfect, but at least it give some indications that the execution will be wrong. And in VS2013 you can double-click on the warning message and it will take you to the error line. Unfortunately where it is defined, not where it is used, but at least it is something rather than no warning at all.

Here is the live example: http://ideone.com/qJpYUQ

NOTE: Note on the code below....the pragma messages in WIN32 version works in VS2013, but I am not sure about GCC. It compiles, but I can't see the warning messages on Ideone.com. So it may need a few tweaks if I did not get the pragma right for gcc.

Again thanks @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{});
}
user3072517
  • 513
  • 1
  • 7
  • 21
  • Rats! The pragma warnings were a nice idea, but once I started adding functions for real, warnings were being generated even though it wasn't actually being used. Ah well....was a nice thought. – user3072517 Mar 20 '15 at 23:04