c++variadic-templatesvariadic-functionsfunctor

Searching a way to use different variadic functors basing on condition


I have a runtime boolean verbose that if false excludes some prints to stdout. To fix ideas, consider to have a lot of parts like:

void add(const int a, const int b, const bool verbose)
{
    //...
    if(verbose) std::print("{}+{}={}\n", a, b, a+b);
}

My goal is to have something like:

template<typename F>
void add(const int a, const int b, F print_if_verbose)
{
    //...
    print_if_verbose("{}+{}={}\n", a, b, a+b);
}

The motivation is to simplify the code and make it less error prone (forgetting to test verbose each print), but also to avoid the various subsystems to be aware of the verbose parameter.

My first attempts are (godbolt):

template<typename F>
void add(const int a, const int b, F print_if_verbose)
{
    print_if_verbose("{}+{}={}\n", a, b, a+b);
}

int main(const int, const char* const argv[])
{
    const bool verbose = get_verbose_arg(); // runtime boolean

    // This doesn't compile, types are different
    //const auto vprint = verbose ? [](const std::string_view msg, const auto&... args){std::vprint_unicode(msg, std::make_format_args(args...));}
    //                            : [](const std::string_view, const auto&...){};
    //add(1, 2, vprint);

    // This works, but ugly
    const auto verb_print = [](const std::string_view msg, const auto&... args){ std::vprint_unicode(msg, std::make_format_args(args...)); };
    const auto no_print = [](const std::string_view, const auto&...){};
    if(verbose) add(2, 3, verb_print);
    else        add(2, 3, no_print);

    // This works, but tests 'verb' each call
    const auto maybe_print = [verb=verbose](const std::string_view msg, const auto&... args){ if(verb) std::vprint_unicode(msg, std::make_format_args(args...)); };
    add(3, 4, maybe_print);
}

Is there a way that I'm missing to obtain this assigning the functor once without testing verbose each time?


Solution

  • You can make the verb_print/no_print pair easier to use with a function like this:

    constexpr decltype(auto) constexpr_promote(bool x, auto&& f) {
        if (x) {
            return std::forward<decltype(f)>(f).template operator()<true>();
        } else {
            return std::forward<decltype(f)>(f).template operator()<false>();
        }
    }
    
    // ...
    
    constexpr_promote(verbose, []<bool VERB>{
        add(3, 4, []<typename... Args>(std::format_string<Args...> msg, Args&&... args){
            if constexpr (VERB)
                std::print(msg, std::forward<Args>(args)...);
        });
    });
    

    This is essentially the same code (there are two lambdas generated based on the two templates stamped out), but you don't need to repeat some stuff