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

How to use source_location in a variadic template function?

提问人:L. F. 提问时间:8/19/2019 最后编辑:einpoklumL. F. 更新时间:8/31/2022 访问量:5983

问:

C++20 功能用于捕获有关调用函数的上下文的信息。 当我尝试将它与可变参数模板函数一起使用时,我遇到了一个问题:我看不到放置参数的位置。std::source_locationsource_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_locationstd::source_location

c++ variadic-templates c++20 默认参数 std-source-location

评论

2赞 Some programmer dude 8/19/2019
也许制作一个宏来调用真正的“调试”函数,并在正确的参数位置(第一个)调用?debugstd::source_location::current()
0赞 eerorika 8/19/2019
关于导致编辑的已删除注释:我们不能在 c++20 的模板中有自动函数参数吗?
1赞 L. F. 8/19/2019
@Someprogrammerdude 这将正常工作,但我认为如果没有更好的方法,这只是一种后备。使用宏在某种程度上违背了IMO:(的目的std::source_location
0赞 L. F. 8/19/2019
@eerorika 是,在参数中是允许的,但我们可以提供 or 作为源位置。auto42"foo"
1赞 L. F. 8/19/2019
@NicolBolas 你是对的,作为一个可以在其值不变的情况下传递的常规对象,这绝对是source_location的一个优势。但我想说,摆脱宏的能力也是一个优势,这就是我“打算”击败的目的。因此,我同意这句话是不完整的,但它不是不完整的,是吗?所以对我来说,这是无稽之谈没有多大意义。(我不知道如何在这里产生错误的格式......

答:

3赞 max66 8/19/2019 #1

这不是一个很好的解决方案,但是......将可变参数放在 a 中怎么样?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赞 max66 8/19/2019
@L.F. - 对不起:也许我误解了:你的意思是你想用模板可变参数函数替换可变参数宏吗?
0赞 L. F. 8/19/2019
我原来的问题根本没有意义。我已经更新了我的问题,使实际问题脱颖而出。忽略可变参数宏。不好意思!
0赞 max66 8/19/2019
@L.F. - 我明白了......好吧,我的答案几乎保持不变,但明确调用的需要使它变得不那么有趣。std::make_tuple()
8赞 einpoklum 8/19/2019 #2

只需将您的参数放在元组中,无需宏。

#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赞 Nicol Bolas 8/19/2019
"这很好用“ 你的意思是,除了你必须把值放在元组中之外?因此必须处理许多毫无意义的语法才能实际提取和使用它们以实现其预期目的?
0赞 einpoklum 8/19/2019
@NicolBolas: s/很多/有点/ ;但是 - 请参阅编辑。
0赞 Nicol Bolas 8/19/2019
这完全取决于你对它们做了什么。在可变参数模板中,将所有值格式化为流是简单且易于阅读的。在您的版本中,两者都不是。这是可行的,但并不漂亮。
0赞 Jarod42 8/19/2019
@NicolBolas:你可能更喜欢这样,但我想说,迭代元组/可变参数模板只是风格上的“问题”。
5赞 Jarod42 8/19/2019 #3
template <typename... Args>
void debug(Args&&... args,
           const std::source_location& loc = std::source_location::current());

“works”,但需要指定模板参数,因为没有 last:

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

    演示

评论

0赞 einpoklum 8/19/2019
我最喜欢你的第一个选择。虽然它是丑陋的代码,但它使用起来最方便,这是最重要的。
70赞 Piotr Skotnicki 8/19/2019 #4

第一种形式可以通过添加扣除指南来工作:

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

演示

评论

0赞 Silicomancer 10/21/2019
普里奥特,我不明白那个语法。你能解释一下吗?
1赞 L. F. 10/21/2019
@Silicomancer 这是一个扣除指南
1赞 KitsuneYMG 8/26/2021
这似乎是一个微不足道的指南。为什么它会改变参数推论?
0赞 L. F. 6/9/2022
@KitsuneYMG 简单地说,由推导指南推导,因此通过构造函数上发生重载解决的时间来知道,因此构造函数可以具有默认参数。Ts…
21赞 ivank 2/28/2021 #5

如果函数在 variadiac 参数之前有一个固定参数,例如 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赞 Xatian 5/15/2021
我喜欢这个解决方案(与演绎指南中公认的答案相比):1.) 如果需要,它允许您手动传递source_location 2.)该函数仍然是一个函数(并且不会成为结构/构造函数调用),它允许您添加 [[ noreturn ]] --如果这应该记录致命错误,>很有用
1赞 chatlanin 3/13/2022 #6

您可以尝试制作:

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

演示

评论

0赞 Muhammad Mohsin Khan 3/15/2022
只有代码的答案不是高质量的。虽然此代码可能很有用,但您可以通过说明它为什么工作、它是如何工作的、何时应该使用它以及它的局限性来改进它。请编辑您的答案以包括解释和相关文档的链接。
1赞 quad 8/29/2022 #7

如果你能接受宏的使用,你可以这样写,以避免显式传入: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赞 HolyBlackCat 8/30/2022
,##是非标准的,符合要求的替代项是 。__VA_OPT__(,)