Как использовать 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");
}

DEMO

Если ваша функция имеет фиксированный параметр перед аргументами с переменным адресом, например строку формата 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);
}

ДЕМО

Другие вопросы по тегам