How to use source_location in a variadic template function?
The first form can be made to work, by adding a deduction guide:
template <typename... Ts>
struct debug
{
debug(Ts&&... ts, const std::source_location& loc = std::source_location::current());
};
template <typename... Ts>
debug(Ts&&...) -> debug<Ts...>;
Test:
int main()
{
debug(5, 'A', 3.14f, "foo");
}
DEMO
Using {fmt} & source_location to create variadic-template-based logging function
You can do it with a struct that represents the format string and location:
#include <fmt/core.h>
#include <source_location>
struct format_string {
fmt::string_view str;
std::source_location loc;
format_string(
const char* str,
const std::source_location& loc =
std::source_location::current()) : str(str), loc(loc) {}
};
void vlog(const format_string& format, fmt::format_args args) {
const auto& loc = format.loc;
fmt::print("{}:{}: ", loc.file_name(), loc.line());
fmt::vprint(format.str, args);
}
template <typename... Args>
void log(const format_string& format, Args&&... args) {
vlog(format, fmt::make_format_args(args...));
}
int main() {
log("invalid squishiness: {}", 42);
}
This prints:
./example.cpp:26: invalid squishiness: 42
Godbolt: https://godbolt.org/z/4aMKcW
C++20 std::source_location yield different column numbers between free function and template functions
I file a PR 99672 to GCC Bugzilla. Jakub Jelinek (one of the GCC contributor) reply me:
I think the standard doesn't specify anything about what exactly the
column should be, so using different columns isn't standard violation.
but he still did a patch to fix it.
Combining function parameter pack and default arguments
The solution would be to us C++20's std::source_location
:
#include <iostream>
#include <source_location>
template<typename... Targs, auto location = 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
Are there any pitfalls to simulate defaulted function parameters using aggregate initialization?
There is at least one pitfall with lifetime (extension):
const std::string& str = "Hello, world!";
create dangling pointer, (No lifetime extension for member).
Following is fine:
void repeat_print(const std::string& str = "Hello, world!", int t = 1) {/*..*/}
int main()
{
repeat_print();
}
but following is not:
struct bar_t {
const std::string& str = "Hello, world!";
int t = 1;
void operator() () const { /*..*/ }
};
int main()
{
bar_t{}();
}
You might fix bar_t
to take member by value, but then you would do extra copy in some cases.
Deducing variadic template arguments with default arguments
You cannot use CTAD on some arguments, but not on the others. You could make a similar syntax work though using tag dispatch. You need to move the specification of the log level to the argument list to accomplish this.
Note that in the following example I'm using std::source_location
(C++20) to be compiler independent:
enum class LogLevel
{
INFO
};
template<LogLevel logLevel>
struct LogLevelInfoTag {
constexpr operator LogLevel()
{
return logLevel;
}
};
constinit LogLevelInfoTag<LogLevel::INFO> INFO;
template<LogLevel logLevel, typename ...str>
struct log {
log(
LogLevelInfoTag<logLevel>,
str &&...args,
std::source_location location = std::source_location::current()
) {
std::cout << "[" << location.file_name() << "] [" << location.function_name() << "] [" << location.line() << "] ";
((std::cout << args << " "), ...);
std::cout << std::endl;
}
};
template<LogLevel logLevel, typename ...str>
log(LogLevelInfoTag<logLevel>, str &&...args) -> log<logLevel, str ...>;
// log<INFO> inst("THIS DOESN'T WORK", "ASD", "ASD"); // (desired syntax)
log inst(INFO, "THIS DOESN'T WORK", "ASD", "ASD");
std::source_location as non type template parameter
std::source_location
is specified as:
struct source_location {
// ...
private:
uint_least32_t line_; // exposition only
uint_least32_t column_; // exposition only
const char* file_name_; // exposition only
const char* function_name_; // exposition only
};
And the rules for the kinds of types that can be used as non-template template parameters require that a type be structural, which means, from [temp.param]/7, emphasis mine:
A structural type is one of the following:
- a scalar type, or
- an lvalue reference type, or
- a literal class type with the following properties:
- all base classes and non-static data members are public and non-mutable and
- the types of all bases classes and non-static data members are structural types or (possibly multi-dimensional) array thereof.
source_location
does not have all of its non-static data members public, so it is not structural, so it is not usable as a non-type template parameter.
This part:
template <auto src_loc = std::experimental::source_location::current().file_name()>
does not work because of [temp.arg.nontype]/3:
For a non-type template-parameter of reference or pointer type, or for each non-static data member of reference or pointer type in a non-type template-parameter of class type or subobject thereof, the reference or pointer value shall not refer to or be the address of (respectively):
- [...],
- a string literal object ([lex.string]),
- ...
But what you can do is create your own type, that is structural, that is constructible from source_location
. It's just that the strings can't be char const*
, they have to own the data. If you look in the examples in P0732, we can build up:
template <typename Char, size_t N>
struct basic_fixed_string { ... };
template <basic_fixed_string S> struct A {};
using T = A<"hello">;
That might be awkward to deal with in this case, so you could also just pick some reasonable max size and go with that.
Macros not expanding properly
It seems that MSVC does not expand __VA_ARGS__
into multiple arguments (see this related question: MSVC doesn't expand __VA_ARGS__ correctly)
This seems to work:
#define GET_MACRO(_1, _2, NAME, ...) NAME
#define XGET_MACRO(args) GET_MACRO args
#define AL_ASSERT(...) XGET_MACRO((__VA_ARGS__, AL_ASSERT_MSG, AL_ASSERT_NO_MSG))(__VA_ARGS__)
And if you are targeting C++20 (and have Visual Studio 2019 version 16.5), you can use __VA_OPT__
:
#define AL_ASSERT(x, ...) if (!(x)) { assertDialog(__VA_OPT__( (__VA_ARGS__) , ) __LINE__, __FILE__); __debugbreak(); }
Or you can do it with some functions:
#define AL_ASSERT(x, ...) (([](bool b, const auto*... msg) { \
static_assert(sizeof...(msg) <= 1, "AL_ASSERT passed too many arguments"); \
if (!b) { \
if constexpr (sizeof...(msg) == 1) { \
const char* m{msg...}; \
assertDialog(msg, __LINE__, __FILE__); \
} else { \
assertDialog(__LINE__, __FILE__); \
} \
__debugbreak();
})(x, __VA_ARGS__))
And with version 16.10 you can do it without a macro with std::source_location
:
void AL_ASSERT(bool x, const std::source_location location = std::source_location::current()) {
if (!x) {
assertDialog(location.line(), location.file_name());
__debugbreak();
}
}
void AL_ASSERT(bool x, const char* msg, const std::source_location location = std::source_location::current()) {
if (!x) {
assertDialog(msg, location.line(), location.file_name());
__debugbreak();
}
}
Related Topics
Constexpr Initializing Static Member Using Static Function
What Is the Most Elegant Way to Read a Text File with C++
How to Overload the Operator++ in Two Different Ways for Postfix A++ and Prefix ++A
C++ Reading the Data Part of a Wav File
How to Construct a Std::String from a Std::Vector<Char>
In C++11, Does 'I += ++I + 1' Exhibit Undefined Behavior
Error: Variable "Cannot Be Implicitly Captured Because No Default Capture Mode Has Been Specified"
Is There Any Compiler Barrier Which Is Equal to Asm("" ::: "Memory") in C++11
The Precision of Std::To_String(Double)
G++ Does Not Show a 'Unused' Warning
Can the Use of C++11's 'Auto' Improve Performance
C++: Where to Initialize Variables in Constructor
How to Change String into Qstring
Do Current X86 Architectures Support Non-Temporal Loads (From "Normal" Memory)
How to Iterate Over a Priority_Queue
Force All Classes to Implement/Override a 'Pure Virtual' Method in Multi-Level Inheritance Hierarchy