4

I have a function with a parameter pack:

template<typename... Targs>
void tprintf(const char* format, Targs... args) {}

(the implementation shouldn't matter, just the signature). I want to add source position as default argument, using the GCC/Clang builtins. Something like

template<typename... Targs>
void tprintf(const char* format, Targs... args,
             const char* file = __builtin_FILE(),
             unsigned line = __builtin_LINE()) {}

This compiles, but calls to it are not passing parameters to args as I hoped; e.g.

tprintf("%d%s", 0, "a");

gives (on Clang 10)

<source>:7:5: error: no matching function for call to 'tprintf'
    tprintf("%d%s", 0, "a");    
    ^~~~~~~
<source>:2:6: note: candidate function template not viable: no known conversion from 'const char [2]' to 'unsigned int' for 3rd argument
void tprintf(const char* format, Targs... args,    
     ^

which seems to indicate args is empty, 0 is file, and "a" is line.

Actually, while writing the question I've found that explicitly passing Targs works:

tprintf<int, char*>("%d%s", 0, "a");

Is it possible to avoid this?

Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
  • clang and gcc compiles ok for me, if i `#define` both `__builtin_FILE()__` and `__builtin_LINE()__` – djacob Jun 05 '20 at 14:10
  • 2
    Near duplicate of [How to use source_location in a variadic template function?](https://stackoverflow.com/q/57547273/3953764) – Piotr Skotnicki Jun 05 '20 at 14:16

2 Answers2

5

The solution would be to us C++20's std::source_location:

#include <iostream>
#include <source_location>

template<typename... Targs, auto location = []{ return source_location::current(); }()>
auto tprintf(char const* format, Targs const&... args) -> void {
    std::cout
        << std::format("{}:{}: ", location.file_name(), location.line())
        << std::format(format, args...);
}

auto main() -> int {
    tprintf("hello {}", "world"); // prints "example.cpp:12: hello world"
}

If C++20 is not an option (especially at this current time, compilers don't support source location) then there is a way to do it without macros. You can simply do a little indirection over the compiler built-ins.

You cannot put defaulted arguments after the variadic arguments, but you can put them in the constructor of the first parameter to have the same effect:

#include <cstdio>

struct location_and_format {
    constexpr location_and_format(
        char const* _format,
        char const* _file_name = __builtin_FILE(),
        unsigned _line = __builtin_LINE()
    ) noexcept : format{_format}, file_name{_file_name}, line{_line} {}

    char const* format;
    char const* file_name;
    unsigned line;
};

template<typename... Targs>
void tprintf(location_and_format format, Targs... args) {
    printf("%s:%u: ", format.file_name, format.line);
    printf(format.format, args...);
}

int main() {
    tprintf("hello %s", "world"); // prints example.cpp:22: hello world
}

Live example

Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
2

How about using variadic macros?

template<typename... Targs>
void tprintfhelper(const char* f, int l, const char* format, Targs... ) 
{
    std::cout << format << " " << l << " " << f << std::endl;
}

#define tprintf(...) tprintfhelper(__builtin_FILE(), __builtin_LINE() __VA_OPT__(,) ##__VA_ARGS__)

int main()
{
    tprintf("%d", 1, 2, 3, 4, 5, "dog");
}
A.Hristov
  • 481
  • 1
  • 4
  • 13