1

I am currently working on a threaded Logging library as the first part of a bigger project for graphics drawing (for personal learning and development of skills).

currently i am using a singleton with a thread running on the side and taking Log messages and data into queues (so that they are not blocking events) to be processed later. I wrote a small wrapper around std::map<> as LogData that can be displayed by the logger in a stream or file in the following way.

[0.000155][DEBUG]: FILE LOGGER ADDED { ID="1" LVL="TRACE" }

The constructors allows to pass string,char*,floats,int,short long etc and will convert it to a string to be displayed later in thoses brackets.

Currently constructing this LogData is a bit bloaty. Example that produced the log above:

GG::LogData id_dat;
id_dat.push("ID", id);
id_dat.push("LVL",GG::loglevel_toString(lvl));
GG_DEBUG("FILE LOGGER ADDED", id_dat);

Since my Class is a Singleton i use macro's to allow for ease of use they are all the same as :

#define GG_TRACE(MESS, ...) GG::Logging::get()->push_to_queue(GG::LOG_LEVEL::TRACE, MESS, ##__VA_ARGS__);

This works fine for most use. But i wanted to make it possible to use on one line, and make it less bloaty. the effect i wanted to achieve was something like this :

//Desired Usage
GG_TRACE("VARIADIC TEST", {"X","1"}, {"Y","2"}, {"Z","3"});

this is expanded here:

void Logging::push_to_queue(GG::LOG_LEVEL level, std::string mess, std::pair<const char*, std::string> log_data ...)

I would use brace initialized list to produce the log data, then i would loop over the variadic argument and construct the LogData in a function instead of having to do it manually every time.

i had it working directly with a function like so.

void test(std::pair<char*,int> p) {
    GG::LogData dat;
    dat.push("key", p.first);
    dat.push("value", p.second);
    GG_TRACE("PAIR: ", dat);
}
// In main...
test({ "test",1 });

and that worked fine. But when i try to use that same pattern and have the macro's forward it to the push_to_queue fonction i get the following error with GCC. GCC compile error

Anybody have ever used brace enclosed initializer list in this way or know how to fix this bug ? i am fairly new to this kind of pattern. Any other suggestion or pointers to improve on this is appreciated. (sorry for the long post)

Ronin825
  • 109
  • 1
  • 2
  • 11
  • In my experience (using Google test) macros and braced initializers don't play nice. – Taekahn Aug 23 '20 at 18:14
  • Wrong idea! Macros are processed before anything of c or c++ code is parsed. There is no trick to pass something from c++ back to macros. BTW: Stop using macros! We are in 2020 and it should be possible to write some simple function which takes any number of different types as you like, – Klaus Aug 23 '20 at 18:30
  • @Klaus Any types ... but a braced initializer list doesn't have a type, and always means template argument deduction is skipped for the parameter/argument pair, so a straightforward variadic template won't be nice to use, at least with the desired syntax. – aschepler Aug 23 '20 at 18:38
  • @Klaus Any suggestion, for adjustements ? – Ronin825 Aug 23 '20 at 18:39
  • @aschepler what do you suggest i do in this case ? Stick with the macro's ? Any other pattern to suggest ? – Ronin825 Aug 23 '20 at 18:41
  • Maybe something like: https://brevzin.github.io/c++/2019/12/02/named-arguments/ – Klaus Aug 23 '20 at 18:42

1 Answers1

1

Note the declaration

void Logging::push_to_queue(GG::LOG_LEVEL level, std::string mess,
    std::pair<const char*, std::string> log_data ...);

does not have a variable number of parameters with type std::pair<const char*, std::string>. It's actually equivalent to the version with an added comma:

void Logging::push_to_queue(GG::LOG_LEVEL level, std::string mess,
    std::pair<const char*, std::string> log_data,
    ...);

which has one parameter of type std::pair<const char*, std::string>, and is a C-style variadic function, with arguments after the third only available via the <cstdarg> macros va_start, va_arg, and va_end. This can't be what you want, since there's no good way for such a function to know how many arguments there were, and since a braced list can never be an argument matching the C-style ellipsis.

The only way to get a C++-style variadic function which knows the number (and types) of arguments is as a template with a variadic template parameter. But a braced list as argument means no template argument deduction, so it would be tricky to make this nice to use.

But we can get this syntax working using a std::initializer_list, plus adding more {} in the macro:

#include <initializer_list>
#include <utility>
#include <string>

void GG::Logging::push_to_queue(GG::LOG_LEVEL level, std::string mess,
    std::initializer_list<std::pair<const char*, std::string>> log_data)
{
    GG::LogData dat;
    for (const auto &kv : log_data)
        dat.push(kv.first, kv.second);
    // Do the rest...
}

#define GG_TRACE(MESS, ...) (GG::Logging::get()->push_to_queue( \
     GG::LOG_LEVEL::TRACE, MESS, {__VA_ARGS__}))

So the expansion will have an argument like {{"X","1"}, {"Y","2"}, {"Z","3"}}, where the outer {} are for the std::initializer_list and the inner {} for each std::pair.

aschepler
  • 70,891
  • 9
  • 107
  • 161
  • That indeed worked as i woudl like to, the extra {} are indeed a bit less nice to use, but it is much nicer than the previous use. Thx. But i used to be able to use the macro either : GG_TRACE("some message") or GG_TRACE("some message", someLogData) altho the use with logData or {{},{}}works, now the simple one without data does not. The compiler throws a call is ambiguous error – Ronin825 Aug 23 '20 at 19:08
  • You don't need to type the extra `{}`, since the `GG_TRACE` adds them in. See example: http://coliru.stacked-crooked.com/a/90d6eee2954bdc45 Not sure of the cause of the "ambiguous" - are there other overloads? – aschepler Aug 23 '20 at 19:34
  • Oh wow, thats great ! now i get what its doing thx ! But im stil trying to fix that ambiguous call thing..... right now im looking into this.... https://stackoverflow.com/questions/3046889/optional-parameters-with-c-macros – Ronin825 Aug 23 '20 at 19:41
  • somehow that macro does not work for me....it either complains about a missing } or an ambiguous call to the overloaded push_to_queue function – Ronin825 Aug 23 '20 at 21:44
  • somehow that macro does not work for me....well, It works when im adding the initializer_list, but the one with only a message does not work and throws a "overloaded push_to_queue is ambiguous" error – Ronin825 Aug 23 '20 at 22:23
  • So there's more than one `push_to_queue` function? – aschepler Aug 24 '20 at 00:42
  • yes there is, but i found the problem, my push_to_queue fonction didnt not have a const char* overload. – Ronin825 Aug 24 '20 at 00:48
  • Also do you think it woudl be at all possible to overload that push_to_queue fonction multiple times to allows to pass the bracket enclosed initialized list with all the base type as values ? – Ronin825 Aug 24 '20 at 00:58
  • This is getting too abstract for me to understand. Maybe you should post another question about the overloading issue. – aschepler Aug 24 '20 at 01:01