简体   繁体   中英

C Variadic function in a C++ wrapper

I am rewriting a C wrapper around a C Python API (Python 1.5) and I noticed that the function Py_VaBuildValue uses variadic number of args. I wondered if I have to use the same in my C++ function wrapper to pass to this function or if there is a more C++ way to deal with this?

I know variadic functions can be the cause of untold trouble, so I'd rather avoid having to use if there is a better way.

EDIT:

So here is the C code I need to make into a C++ function:

int Set_Global(char *modname, char *varname, char *valfmt, ... /* cval(s) */) {

    int result;
    PyObject *module, *val;                             // "modname.varname = val"
    va_list cvals;
    va_start(cvals, valfmt);                            // C args after valfmt

    module = Load_Module(modname);                      // get/load module
    if (module == NULL) 
        return -1;
    val = Py_VaBuildValue(valfmt, cvals);               // convert input to Python
    va_end(cvals);
    if (val == NULL) 
        return -1;
    result = PyObject_SetAttrString(module, varname, val); 
    Py_DECREF(val);                                     // set global module var
    return result;                                      // decref val: var owns it
}

So I'm making the same function with std::string instead of char* and I want to change the ellipsis to something more c++ like, that I can however then pass to Py_VaBuildValue inside the function.

If you want to be clever and don't fear some heavy template wizardry, it should be possible to generate (or massage) valfmt to always match the types you want to pass (I am assuming it uses format specifiers similar to printf, but the technique is applicable to any kind of format specification). You could do something like:

template <typename T> struct format_trait;
template <> struct format_trait<int> { static const char * format() { return "%i"; }};
template <> struct format_trait<unsigned> { static const char * format() { return "%u"; }};
... // and so on for each type you want to use

template <typename Arg1>
int Set_Global(const std::string &modname, const std::string &varname, const Arg1 &arg1)
{
    return ::Set_Global(modname.c_str(), varname.c_str(), format_trait<Arg1>::format(), arg1);
}

template <typename Arg1, typename Arg2>
int Set_Global(const std::string &modname, const std::string &varname, const Arg1 &arg1, const Arg2 &arg2)
{
    return ::Set_Global(modname.c_str(), varname.c_str(),
        std::string(format_trait<Arg1>::format()) + format_trait<Arg2>::format(),
        arg1, arg2);
}
... // Repeat up to number of argument you reasonably expect to need or use C++0x variadic templates.

This is the simple way where each value is formatted the default way and combined together. If you want something more complex, you can create a function, that will get valfmt string and correct format specifiers (obtained from the trait) and will fix up the format string to match.

You may write template function which will check the correctness of params, and won't allow your program to crash.

bool BuildValueCheck(const char * s, int v)
{
    if( s[0] == 'i' )
        return true;
    return false;
}
bool BuildValueCheck(const char * s, float v)
{
    if( s[0] == 'f' )
        return true;
    return false;
}
bool BuildValueCheck(const char * s, char * v)
{
    if( s[0] == 's' || s[0] == 'z' )
        return true;
    return false;
}
// and so on for each other type
template<typename t1>
PyObject *BuildValue(char * format, t1 v1)
{
     char * s = strchr(format, "ifsz...."); // Skip here all "()[]" etc
     if( !s )
         return NULL; // and print an error
     if(!BuildValueCheck(s, v1))
         return NULL; // and also print an error
     return Py_BuildValue(format, v1);
}

template<typename t1, typename t2>
PyObject *BuildValue(char * format, t1 v1, t2 v2)
{
     // Skip here all "()[]" etc
     char * s = strchr(format, "ifsz....");
     if( !s )
         return NULL;
     if(!BuildValueCheck(s, v1))
         return NULL;
     s = strchr(s+1, "ifsz....");
     if( !s )
         return NULL;
     if(!BuildValueCheck(s, v2))
         return NULL;
     return Py_BuildValue(format, v1, v2);
}
// and so on for 3,4,5 params - I doubt your program uses more
// and then replace all Py_BuildValue with BuildValue across the code, or make a #define 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM