Как использовать source_location в функции шаблона variadic?
Особенность C++20 std::source_location
используется для сбора информации о контексте, в котором вызывается функция. Когда я пытался использовать его с функцией шаблона переменной, я столкнулся с проблемой: я не могу найти место для размещения source_location
параметр.
Следующее не работает, потому что переменные параметры должны быть в конце:
// doesn't work
template <typename... Args>
void debug(Args&&... args,
const std::source_location& loc = std::source_location::current());
Следующее также не работает, потому что вызывающий будет облажен параметром, вставленным между:
// doesn't work either, because ...
template <typename... Args>
void debug(const std::source_location& loc = std::source_location::current(),
Args&&... args);
// the caller will get confused
debug(42); // error: cannot convert 42 to std::source_location
Мне сообщили в комментарии, что std::source_location
работает без проблем с шаблонами, но я не могу понять, как это сделать. Как я могу использовать std::source_location
с переменными шаблонными функциями?
7 ответов
template <typename... Ts>
struct debug
{
debug(Ts&&... ts, const std::source_location& loc = std::source_location::current());
};
template <typename... Ts>
debug(Ts&&...) -> debug<Ts...>;
Контрольная работа:
int main()
{
debug(5, 'A', 3.14f, "foo");
}
Если ваша функция имеет фиксированный параметр перед аргументами с переменным адресом, например строку формата printf, вы можете обернуть этот параметр в структуру, которая фиксирует source_location в своем конструкторе:
struct FormatWithLocation {
const char* value;
std::source_location loc;
FormatWithLocation(const char* s,
const std::source_location& l = std::source_location::current())
: value(s), loc(l) {}
};
template <typename... Args>
void debug(FormatWithLocation fmt, Args&&... args) {
printf("%s:%d] ", fmt.loc.file_name(), fmt.loc.line());
printf(fmt.value, args...);
}
int main() { debug("hello %s\n", "world"); }
Просто поместите ваши аргументы в кортеж, макрос не нужен.
#include <source_location>
#include <tuple>
template <typename... Args>
void debug(
std::tuple<Args...> args,
const std::source_location& loc = std::source_location::current())
{
std::cout
<< "debug() called from source location "
<< loc.file_name() << ":" << loc.line() << '\n';
}
И это работает *.
Технически вы могли бы просто написать:
template <typename T>
void debug(
T arg,
const std::source_location& loc = std::source_location::current())
{
std::cout
<< "debug() called from source location "
<< loc.file_name() << ":" << loc.line() << '\n';
}
но тогда вам, вероятно, придется прыгнуть через несколько обручей, чтобы получить типы аргументов.
* В связанном примере я использую <experimental/source_location>
потому что это то, что компиляторы принимают прямо сейчас. Кроме того, я добавил код для печати аргумента кортежа.
template <typename... Args>
void debug(Args&&... args,
const std::source_location& loc = std::source_location::current());
"работает", но требует указать аргументы шаблона, поскольку они не выводятся, поскольку они не являются последними:
debug<int>(42);
Возможные (не идеальные) альтернативы включают в себя:
использовать перегрузки с жестко заданным пределом (старый возможный способ "обрабатывать" вариады):
// 0 arguments void debug(const std::source_location& loc = std::source_location::current()); // 1 argument template <typename T0> void debug(T0&& t0, const std::source_location& loc = std::source_location::current()); // 2 arguments template <typename T0, typename T1> void debug(T0&& t0, T1&& t1, const std::source_location& loc = std::source_location::current()); // ...
положить
source_location
на первой позиции, без дефолта:template <typename... Args> void debug(const std::source_location& loc, Args&&... args);
а также
debug(std::source_location::current(), 42);
аналогично перегрузкам, но просто используйте кортеж как группу
template <typename Tuple> void debug(Tuple&& t, const std::source_location& loc = std::source_location::current());
или же
template <typename ... Ts> void debug(const std::tuple<Ts...>& t, const std::source_location& loc = std::source_location::current());
с использованием
debug(std::make_tuple(42));
Не очень хорошее решение, но... как насчет размещения аргументов в std::tuple
?
Я имею в виду... что-то как
template <typename... Args>
void debug (std::tuple<Args...> && t_args,
std::source_location const & loc = std::source_location::current());
К сожалению, таким образом, вы должны явно позвонить std::make_tuple
называя это
debug(std::make_tuple(1, 2l, 3ll));
Если вы можете принять использование макросов, вы можете написать это, чтобы избежать явной передачиstd::source_ location::current()
:
template <typename... Args>
void debug(const std::source_location& loc, Args&&... args);
#define debug(...) debug(std::source_location::current() __VA_OPT__(,) __VA_ARGS__)
Вы можете попробовать сделать это:
#include <iostream>
#include <experimental/source_location>
struct log
{
log(std::experimental::source_location location = std::experimental::source_location::current()) : location { location } {}
template<typename... Args>
void operator() (Args... args)
{
std::cout << location.function_name() << std::endl;
std::cout << location.line() << std::endl;
}
std::experimental::source_location location;
};
int main()
{
log()("asdf");
log()(1);
}