如何在可变参数模板函数中使用source_location?

68
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


2
也许可以将 debug 定义为一个宏,该宏将在正确的参数位置(首个)使用 std::source_location::current() 调用真正的“debug”函数? - Some programmer dude
1
@Someprogrammerdude 那种方法可以正常工作,但我认为只有在没有更好的方法时才考虑使用它。使用宏有点违背了 std::source_location 的初衷,我的意见是这样的。 - L. F.
@L.F. 可以是一个有用的自定义点,让调用者使用特殊的函数名称。 - eerorika
@Acorn:https://en.cppreference.com/w/cpp/experimental/source_location/source_location 仍然表明是constexpr。 - JVApen
1
@NicolBolas 您是正确的,source_location作为一个常规对象可以传递并保持其值不变的优点确实很大。但我认为摆脱宏的能力也是一种优势,这也是我“打算”打败的目的。因此,我同意该句子不完整,但它并不是不正确的,对吗?所以对我来说,它没有多大意义,它是无意义的。(我不知道如何在这里产生错误的格式...) - L. F.
显示剩余11条评论
7个回答

70

通过添加推断指南,可以使第一种形式正常工作:

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");
}

演示


Priotr,我不理解那个语法。你能稍微解释一下吗? - Silicomancer
1
@Silicomancer 这是一个推断指南 - L. F.
1
这似乎是一个琐碎的指南。为什么它会改变参数推断? - KitsuneYMG
@KitsuneYMG 简单来说,Ts… 是由推导指南推导出来的,因此在构造函数上进行超载解析时已知,因此构造函数可以具有默认参数。 - L. F.

21

如果您的函数在可变参数之前有一个固定的参数,比如 printf 格式字符串,那么您可以在结构体中包装该参数,并在其构造函数中捕获 source_location:

如果您的函数在可变参数之前有一个固定的参数,比如 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"); }

2
我喜欢这个解决方案(相对于带有扣除指南的已接受答案):1)如果需要,它允许您手动传递源位置;2)该函数仍然保持为函数(而不是成为结构体/构造函数调用),这使您可以添加[[noreturn]]--> 如果此函数应记录致命错误,则非常有用。 - Xatian

8
只需将参数放入元组中,无需宏。
#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>,因为目前编译器接受这种方式。此外,我添加了一些代码来打印参数元组。


7
this works just fine” 的意思是“这很好用”,除了你需要把值放在元组里面,然后不得不处理大量无用的语法才能真正提取和使用它们来达到预期的目的。 - Nicol Bolas
@NicolBolas:将“很多”替换为“一点”;但请参见编辑。 - einpoklum
这完全取决于你对它们的使用方式。在可变参数模板中,将所有值格式化到流中非常简单且易于阅读。但在你的版本中,却不是这样。虽然可以实现,但并不美观。 - Nicol Bolas
@NicolBolas:你可能更喜欢这个,但我认为在元组/可变参数模板上进行迭代只是一种风格上的“问题”。 - Jarod42

5
template <typename... Args>
void debug(Args&&... args,
           const std::source_location& loc = std::source_location::current());

“works”可以使用,但需要指定模板参数,因为存在非可推导的参数,它们不是最后一个参数:
debug<int>(42);

演示

可能的(不完美)替代方案包括:

  • use overloads with hard coded limit (old possible way to "handle" variadic):

    // 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());
    
    // ...
    

    Demo

  • to put source_location at first position, without default:

    template <typename... Args>
    void debug(const std::source_location& loc, Args&&... args);
    

    and

    debug(std::source_location::current(), 42);
    

    Demo

  • similarly to overloads, but just use tuple as group

    template <typename Tuple>
    void debug(Tuple&& t,
               const std::source_location& loc = std::source_location::current());
    

    or

    template <typename ... Ts>
    void debug(const std::tuple<Ts...>& t,
               const std::source_location& loc = std::source_location::current());
    

    with usage

    debug(std::make_tuple(42));
    

    Demo


我最喜欢你的第一个替代方案。虽然它的代码很丑陋,但是它是最方便使用的,这才是最重要的。 - einpoklum

3

虽然不是最好的解决方案,但是……把可变参数放到一个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));

1
@L.F. - 对不起:也许我误解了:您的意思是想用模板可变参数函数替换可变宏吗? - max66
我的原始问题根本没有意义。我已经更新了我的问题,以使实际问题突出。请忽略可变参数宏。抱歉! - L. F.
@L.F. - 我明白了...我的答案基本上没有变化,但需要显式调用std::make_tuple()使其变得不那么有趣。 - max66

1
你可以尝试制作它:
#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);
}

演示


“仅有代码的答案并不是高质量的答案”。虽然这段代码可能很有用,但您可以通过解释它为什么有效、如何有效、何时应该使用以及其限制是什么来改进它。请编辑您的答案,包括解释和相关文档链接。 - Muhammad Mohsin Khan

1

如果您可以接受使用宏,您可以编写以下内容,以避免显式传递 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__)

1
,## 是非标准的写法,符合规范的替代方式是 __VA_OPT__(,) - HolyBlackCat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接