2

I have a custom logger that uses a logger->logf(format, ...) style varargs method for logging. I have object handles that are wrappers around pointers. I have a special format specifier to print the objects ( with a toString() method like in Java)

The "handles" are not "trivially copyable" as they can be constructed from a number of input types, and have converter-cast operators that return the pointer to the contained object type. ( similar to ComPtr<> )

IN windows C++ I can just pass the handle in and the varags method sees the pointer. GCC considers this an error now. The handle is "sizeof(void *)"

Is there a way to get GCC to allow this on a varags method? Is there a way to specify a special operator xx () method to be passed into a varargs method?

I have a rather large library with a lot of log lines in it and redoing all of them with operator + or operator << would be an intense chore.

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Well if does look like the Variadic template is what I need to use but I have yet to figure out how to make it simple an elegant.

The essence of an algorithm would be

place object or buffer on stack (ideally based on the number of arguments) add all the arguments to the object. on the last argument add it to the object, as well and process the arguments.

The "recursive" nature of a variadic template makes a nice way to do this a bit to figure out.

#

Ok I bit the bullet and rewrote the formatter part to use variadic templates. It did take 3 days. Basically it involves having an "Arg" class that has a union of all the primitive types and a "type" field set by the constructor overload for each type.

Then a variadic template to "load a list" of args which is passed to the formatters equivalent of "vsprintf". Great as there is enough information to be runtime type safe. The quastion now is HOW much code bloat is there with the template expansion. As all they do is cast and the "Arg" is a fixed size and it's constructor just loads two fields, type and value. Will GCC and MSC nicely optimize all of it out so there aren't 2 ^ maxArgs full expansions of the variadic templates.

template <typename... Args>
int writef(const wchar_t *fmt, ...) {
    FormatfArglist<sizeof...(Args)> arglist;
    FormatfArgBase::addArgs(arglist.args(), args...);
    return(vwritef(fmt, arglist));
}

template <typename First, typename... Rest> 
static void FormatfArgBase::addArgs(Arg *arglist, 
                              const First &first, const Rest &... rest) {            
    setArg(arglist,Arg(first));            
    if(sizeof... (Rest) > 0) {
        addArgs(++arglist, rest...); // recursive call using pack expansion syntax  
    }
}  
peterk
  • 5,136
  • 6
  • 33
  • 47
  • 5
    Variadic template seems more appropriate, C-ellipsis has constraints on accepted parameter. – Jarod42 Dec 18 '17 at 21:58
  • The `va_arg` macro may very well perform an actual bitwise copy to your smart pointer object, bypassing copy constructors, and then *run the destructor* of the copy. This could really mess things up. – Kerrek SB Dec 18 '17 at 22:03
  • Can't you redesign your variable function so it accepts raw object pointers, not custom class types? Then you just take the address at the call site if and as necessary. – Kerrek SB Dec 18 '17 at 22:04
  • Can't you just pass a plain old pointer to the handle itself? That should be `va_arg` safe, and your log function can just dereference the pointer to call the `toString()` method. – cmaster - reinstate monica Dec 18 '17 at 22:06
  • however like printf it handles all the basic primitive types, %d %u %s %f %p etc and all the printf style formatting specifications. If it did take some form of class "autoboxed" object and I had a number of implementations for for different argument counts yes. – peterk Dec 18 '17 at 22:31
  • Of course I can cast the handle to a pointer but would have to do that everywhere it is used. and it destroys the simplicity of things like logf( StringType.format("uses same formatter")); which will destroy the string when the method returns and it's stack frame goes out of scope. These objects are either reference counted or managed so destroying the handle is what is actually desired. – peterk Dec 18 '17 at 22:34
  • and if I cast it to a pointer what can happen is the handle is destroyed before the method is called and the pointer passed in is then invalid. – peterk Dec 18 '17 at 22:35
  • [Related question](https://stackoverflow.com/q/29326460/1362568) and [possibly helpful answer](https://stackoverflow.com/a/29326784/1362568) – Mike Kinghan Dec 20 '17 at 18:52

2 Answers2

0

Seems as the C++ guys pile on restrictions on the old C ellipsis one really has to use Variadic templates to get full functionality.

So I bit the bullet and rewrote the formatter part to use variadic templates. It did take 3 days. Basically it involves having an "Arg" class that has a union of all the primitive types and a "type" field set by the constructor overload for each type.

Then a variadic template to "load a list" of args which is passed to the formatters equivalent of "vsprintf". Great as there is enough information to be runtime type safe. The question now is HOW much code bloat is there with the template expansion. As all they do is cast and the "Arg" is a fixed size and it's constructor just loads two fields, type and value. Will GCC and MSC nicely optimize all of it out so there aren't 2 ^ maxArgs full expansions of the variadic templates?

// This is in a "TextWriter" class
template <typename... Args>
int writef(const wchar_t *fmt, ...) {
    FormatfArglist<sizeof...(Args)> arglist;
    FormatfArgBase::addArgs(arglist.args(), args...);
    return(vwritef(fmt, arglist));
}

template <typename First, typename... Rest> 
static void FormatfArgBase::addArgs(Arg *arglist, 
                          const First &first, const Rest &... rest) {            
    setArg(arglist,Arg(first));            
    if(sizeof... (Rest) > 0) {
        addArgs(++arglist, rest...); // recursive call using pack expansion syntax  
    }
}  
peterk
  • 5,136
  • 6
  • 33
  • 47
-1

I am not sure why it works with Microsoft compiler. The standard clearly indicates, that

Only arithmetic, enumeration, pointer, pointer to member, and class type arguments are allowed.

(http://en.cppreference.com/w/cpp/language/variadic_arguments).

For your particular case, I suggest you use variadic template function as an intermediate step to convert those values to pointers, and than call your Log function (which I imagine can't be template itself).

SergeyA
  • 61,605
  • 5
  • 78
  • 137
  • It can be re-architected for this but I will have to create an "autobox" type argument that can contain all the primtive types and the template will take a list of "autobox" arguments, The argument class will have to handle expanding all the sub int types to int width and all the float types to doubles etc in the way the '...' will promote primitives and pack arguments in a standard way. It's a significant chore to do this. – peterk Dec 18 '17 at 22:51
  • And note that VC emits a warning at warning level 4. – SoronelHaetir Dec 18 '17 at 22:56
  • You aren't saying that cppreference is the standard, are you? – cpplearner Dec 19 '17 at 16:03
  • ... and cppreference actually says class type arguments are allowed. (That's inaccurate since some class types are only conditionally-supported, per [\[expr.call\]/9](http://eel.is/c++draft/expr.call#9.sentence-5), but as it stands it does not seem to support your view at all.) – cpplearner Dec 19 '17 at 16:17