3

I tried to convert this macro based on this article into a constexpr but can't get it right:

#define log_error(...) log_api(__FILE__, __LINE__, log_api_severity::error, /*print_console=*/false, __VA_ARGS__)

Here my last attempt and I am quite discouraged...

template<typename ...Args>
std::result_of<Args...>::type tmpl_call_return_f(Args...&& args)
{
    return log_api(__FILE__, __LINE__, log_api_severity::error, /*print_console=*/false, std::forward<Args>(args)...);
}

The error I got from Visual Studio 2019 is this one:

(struct) result_of<_Fty> in namespace std explain usage

use the 'typename' keyword to treat nontype 'std::result_of<_Fty>::type [with _Fty=Args...]" as a type in a dependent context

I don't understand how to fix the code and what to do from there.

Below a sample that builds with Visual Studio 2019 (I commented out the constexpr and use the macro to give the context of my attempt):

#include <iostream>
#include <cstdarg>

// This is the macro I want to convert into constexpr variadic template?
#define log_error(...) log_api(__FILE__, __LINE__, log_api_severity::error, /*print_console=*/false, __VA_ARGS__)

// Error: use the 'typename' keyword to treat nontype "std::result_of<_Fty>::type[with _Fty=Args...]" as a type in a dependent context
// template<typename ...Args>
// std::result_of<Args...>::type tmpl_call_return_f(Args...&& args)
// {
//     return log_api(__FILE__, __LINE__, log_api_severity::error, /*print_console=*/false, std::forward<Args>(args)...);
// }

// Log severity levels
enum class log_api_severity
{
    error = 0,
    warning = 50,
    info = 51,
    debug = 52,
    trace = 53,
    all = 54,
    disabled = 55,
};

bool log_api(const char* filename, const int line_number, const log_api_severity severity, const bool print_console, const char* fmt, ...)
{
    char     msg[1024] = "";
    va_list  args;
    va_start(args, fmt);
    vsprintf_s(msg, sizeof(msg), fmt, args);

    // write in the log file...
    printf("%s", msg);

    va_end(args);
    return true;
}

int main()
{
    log_error("this is an error message");
    return 0;
}
Less White
  • 561
  • 4
  • 13
  • 6
    Unfortunately, even if you fix the syntax, you will then discover a surprise: every call will use the same `__FILE__` and `__LINE__`, namely the file and line where the variadic template function is defined, which is obviously not what you want. You will need to wait until C++20 to do this right. For now, you have to stick to macros. – Sam Varshavchik Aug 17 '19 at 21:49
  • Oh that's interesting. I assume I hit a special case for which this is fine to use a macro and ignore the compiler recommendation asking me to convert it into an constexpr. Thanks for your help. – Less White Aug 17 '19 at 21:52
  • 1
    @SamVarshavchik I am curious. Why is that? `__FILE__` and `__LINE__` do the expected thing with normal macros – Aykhan Hagverdili Aug 17 '19 at 21:54
  • 2
    @Ayxan because `__FILE__` and `__LINE__` refer to the line and file where they are located when they are replaced by the line and the file. For `#define log_error(...)` each occurrence of `log_error` is first replaced by the `log_api(__FILE__, __LINE__, ...`, so the `__FILE__` and `__LINE__` are acutally at the place where the `log_error(...)` appeared in the code. – t.niese Aug 17 '19 at 21:57
  • @SamVarshavchik Even with C++ 20, I am not sure if it could works without macros if you want variable number of arguments. I think you need default for file/line variables and default must be at the end. As variable number of arguments also need to be at the end. Or I am missing something? – Phil1970 Aug 18 '19 at 01:02
  • 3
    @Phil1970 - the C++20 solution does not use macros, but `std::source_location`, and will work perfectly fine with variadic parameters. Look it up. – Sam Varshavchik Aug 18 '19 at 01:05
  • What (do you think) is “a constexpr”? – Davis Herring Aug 18 '19 at 02:02
  • @SamVarshavchik May I ask what the C++20 feature is called that does this right? – Dean Seo Aug 18 '19 at 04:31
  • @Sam doesn't it require template variadic arguments instead of C-style? – JVApen Aug 18 '19 at 06:25
  • @DeanSeo - I mentioned this feature in my previous comment. – Sam Varshavchik Aug 18 '19 at 13:12
  • @JVApen - pop quiz: in my previous comment, what did I write: it does work, or it doesn't work, with variadic parameters? – Sam Varshavchik Aug 18 '19 at 13:13
  • @Sam: I would assume that the C variant wouldn't work, although I've never used it in real life – JVApen Aug 18 '19 at 13:14
  • 2
    The compiler error message is very informative. `std::result_of::type` is a dependent name and you need to use `typename` before. Other than that, `Args...&& args` should be changed to `Args&&... args`. – L. F. Aug 18 '19 at 17:36
  • Possible duplicate of [Where and why do I have to put the "template" and "typename" keywords?](https://stackoverflow.com/questions/610245/where-and-why-do-i-have-to-put-the-template-and-typename-keywords) – L. F. Aug 18 '19 at 17:52
  • @SamVarshavchik Great. Thanks. – Dean Seo Aug 18 '19 at 23:47
  • @SamVarshavchik I mostly understand how parameter pack and `source_location` work independently. What I am not sure is if we can combine them. If we have `template void f(Args&& ... args, const std::source_location& location = std::source_location::current())` and we call it like that `f(a, b, c)`, **how would the compiler decide if `c` is in the pack or the optional argument for source location?**. That is, what are the rules to decide between an extra argument for the pack or replacing a default value?. – Phil1970 Aug 19 '19 at 01:07
  • Sacrifice three extra characters (not including whitespace) to the gods of C++, by calling `f({}, arg1, arg2, arg3)`, declared as `template void f(const default_source_location &, Args && ...args)`. `default_source_location` is your own helper class whose constructor takes a `std::source_location` parameter with a default value of `current location`, and stashes it inside the class, and `f()` can fetch it out. – Sam Varshavchik Aug 19 '19 at 01:36
  • 2
    @SamVarshavchik We've discovered a way to use `source_location` with variadic templates without cluttering the caller syntax in https://stackoverflow.com/a/57548488. It involves using the constructor of a variadic template class and a deduction guide. – L. F. Aug 21 '19 at 09:01
  • I also considered this wrapping approach, @L.F., but I could not be certain whether `std::source_location` in this approach will always turn out to be the source location of the `debug` template, which will always be the same, of course, or the source location of each individual instantiation of the `debug` template. cppreference.com defines `std::source_location::current` as "corresponding to the location of the call site." To me, the `debug` template is the call site. I suspect that the observed results may vary depending on the compiler. – Sam Varshavchik Aug 21 '19 at 10:38
  • @SamVarshavchik The deduction guide is irrelevant after the class template arguments are deduced, the constructor is called directly. So it should work with no problem. – L. F. Aug 21 '19 at 11:10

0 Answers0