Question

Is the following usage of a lambda wrong, fragile, or silly? It works on VC++ 2012, but I am concerned that there are some variable-argument/lambda stack interactions that make this dangerous.

class
ArgumentException : public std::runtime_error
{
public:
   ArgumentException( 
      const char* format_,
      ... )
      : std::runtime_error(
         [&]()
         { 
            char buffer[2048];
            va_list arguments;
            va_start ( arguments, format_ );  
            int writtenCount = vsnprintf( buffer, 2048, format_, arguments );
            va_end ( arguments );
            return std::string(buffer); 
         }() )
   {
   }
};
Was it helpful?

Solution

The C++11 standard explicitly makes va_list and the supporting macros available through <cstdarg> (§18.10, table 37) but makes no attempt to restrict their use or (re)define their meaning, so I'm going to turn to the C standard.

§7.15.1.4 says that:

void va_start(va_list ap, parmN );

The parameter parmN is the identifier of the rightmost parameter in the variable parameter list in the function definition (the one just before the , ...). If the parameter parmN is declared with the register storage class, with a function or array type, or with a type that is not compatible with the type that results after application of the default argument promotions, the behavior is undefined.

In your case, format_ is not an argument (it's a captured variable in your lambda) and the function in which va_start is called is not even the one with the variable parameter list, so you're arguably very much in the realm of undefined behavior. Not to mention that the argument promotion rules of the C language can't deal with reference types, and therefore it can't correctly deal with the fact that format_ is a reference, not a straight-up pointer.

As far as I know, it's syntactically infeasible to use variadic parameters in a constructor's initializer list. (See this guy below who did it.) You could, however, use variadic templates to forward the parameters to a "clean" C-style variadic parameters function:

#include <cstdarg>
#include <cstdio>
#include <string>

std::string stringprintf(const char* format, ...)
{
    char buffer[0x2000];
    va_list ap;
    va_start(ap, format);
    vsnprintf(buffer, sizeof buffer, format, ap);
    va_end(ap);
    return buffer;
}

class ArgumentException : public std::runtime_error
{
public:
    template<typename... T>
    ArgumentException(const char* format, T... arguments)
    : std::runtime_error(stringprintf(format, arguments...))
    { }
};

Also consider using <stdexcept>'s invalid_argument exception subclass.

OTHER TIPS

I don't recommend doing this, so this is more just an exercise in whether or not it is possible to write a conforming implementation without a variadic template. I think this is, though it's not elegant or pretty:

#include <stdexcept>
#include <cstdarg>

std::string
helper(const char *fmt, va_list args) {
    char buffer[2048];
    int writtenCount = vsnprintf(buffer, sizeof buffer, fmt, args);
    return std::string(buffer);
}

class ArgumentException : public std::runtime_error {
    public:
       ArgumentException(const char* fmt, ...)
           : std::runtime_error(helper(fmt, (va_start(args, fmt), args))) {
           va_end(args);
       }
    private:
        va_list args;
};

By the way, the original attempt gave me an error in g++, claiming that I was using va_start in a function with fixed arguments, which is correct, I believe.

For posterity's sake, adding WhozCraig's solution here:

class ArgumentException : public std::runtime_error
{
private:
    static std::string mkmsg(const char *fmt, ...)
    {
        char buffer[2048];
        va_list args;
        va_start ( args, fmt );
        int writtenCount = vsnprintf( buffer, 2048, fmt, args );
        va_end ( args );
        if (writtenCount<=0)
            *buffer = 0;
        return buffer;
    }

public:
    template<class... Args>
    ArgumentException(const char* fmt, Args&&... args )
        : std::runtime_error( mkmsg(fmt, std::forward<Args>(args)... ) )
    {
    }
};
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top