2

What I am looking for is the log function that will log message, but only once per call site.

It would be useful to log first occurrence of an error, timeout, etc, without spamming the cout/log files.

Traditionally it has been implemented with macros(where they expand to some static bool/counter, you can see LOG_FIRST_N in glog for details), but I wonder if in C++20 it can be done without macros.

What I have tried: Use std::source_location as template param, based on this answer, does not work since magic of std::source_location::current() does not work as I want it to work.

note: I know I can have one static std::set/std::unordered_set of call sites, but I am interested in solutions that is as efficient as the evil MACRO solution.

NoSenseEtAl
  • 28,205
  • 28
  • 128
  • 277

3 Answers3

4

As every lambda expression is of different type, you can use lambda expressions to tag calls to the same function.

For example this:

#include <iostream>


template <typename T,typename F>
void once(T t, F f){
    static bool first = true;
    if (first) {
        f();
        first = false;
    }
} 

int main(){
    for (int i=0; i <1000; ++i){
        once([](){},[](){ std::cout << "A";});
    }
    for (int i=0; i <1000; ++i){
        once([](){},[](){ std::cout << "B";});
    }

}

Prints

 AB

However, once([](){},... is a bit unwieldy and it is still tempting to use a macro.


PS: As mentioned in a comment, since C++20 the lambda can be used as default argument for the template and even before you can do:

#include <iostream>

template <typename F,typename T>
void once_impl(F f, T t){
    static bool first = true;
    if (first) {
        f();
        first = false;
    }
} 

template <typename F>
void once(F f) { once_impl(f,[](){});}

int main(){
    for (int i=0; i <1000; ++i){
        once([](){ std::cout << "A";});
    }
    for (int i=0; i <1000; ++i){
        once([](){ std::cout << "B";});
    }
}
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
3
if(error)
  static const bool error_logged=error_log << "message\n";

the first time error is true, the stream will be written to. Every other time, nothing. It is thread safe; if 2 threads both have the error, it is guaranteed exactly one runs the log code.

error_logged will store true if the write worked, false otherwise.

C++ does not have a way to inject state to the calling location from within a function call (barring coroutines, which are not zero cost). So the state -- "have I called this before, here" -- must be at the calling site.

The amount of state required is minimal; as you can see above, a branch plus a static local is enough.

The evil macros are usually more complex because they want to be usable in more complicated structures, like:

return a?b:(log("error!"), c);
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • I don't think it is thread safe in the usual sense. This only guarantees that the initialization of the **SAME** local static object is thread safe. That means two logging code can have data-race/race-conditions if the logging implementation itself isn't thread safe. – VainMan Mar 14 '22 at 07:14
  • @vain yes, the code after `error_logged=` will run at most once regardless of how many threads log an error. The printing code can fail to be thread safe with *other* printing code, but this line is thread safe with itself. All thread safety is relational; claims of absolute thread safety is a lie or a simplification. – Yakk - Adam Nevraumont Mar 14 '22 at 21:37
2

You might wrap the logic in template, and in C++20, lambda can be used as default template argument:

So you might do

template <typename F, typename Tag = decltype([](){})>
// or template <typename F, auto Tag = [](){}>
void simple_do_once(F f /*, Tag={}*/ )
{
    static std::once_flag flag;
    std::call_once(flag, f);
}

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Except that is the same lambda at every call, no? I would be surprised it makes a new type at every call site. Not shocked admittedly, but surprised. I'd also be worried about the standard changing its mind if it wasn't really, really explicit about it; it making a distinct type at every call site looks expensive. – Yakk - Adam Nevraumont Dec 16 '20 at 05:25
  • @Yakk-AdamNevraumont: from Demo, it does. – Jarod42 Dec 16 '20 at 09:11
  • sure it works in one version of one compiler, but does the standard mandate it? – Yakk - Adam Nevraumont Dec 16 '20 at 12:28
  • Can't `typename Tag = decltype([](){})` be replaced with `auto tag = []{}`? It will still be a different type for every inantiation (as far as I can tell), so this should also work, shouldn't it? – Fureeish Dec 16 '20 at 13:24
  • 1
    @Fureeish: Both works, prefer even `auto = []{}`, version. but if you want to specify the tag yourself, it seems more convenient to use the `typename` version. – Jarod42 Dec 16 '20 at 15:45