【问题标题】:How to use source_location in a variadic template function?如何在可变参数模板函数中使用 source_location?
【发布时间】:2019-12-24 02:45:00
【问题描述】:

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

我在comment 中得知std::source_location 可以与可变参数模板无缝协作,但我很难弄清楚如何使用。如何将std::source_location 与可变参数模板函数一起使用?

【问题讨论】:

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

标签: c++ variadic-templates c++20 default-arguments std-source-location


【解决方案1】:

第一个表单可以通过添加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...>;

测试:

int main()
{
    debug(5, 'A', 3.14f, "foo");
}

DEMO

【讨论】:

  • 先行者,我不懂那个语法。你能解释一下吗?
  • @Silicomancer 这是一个deduction guide
  • 这似乎是一个微不足道的指南。为什么会改变论据推演?
【解决方案2】:

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

【讨论】:

  • 我喜欢这个解决方案(与推论指南中接受的答案相比):1.)它允许您在需要时手动传递 source_location 2.)该函数保持一个函数(并且不会成为一个结构/构造函数调用),它允许您添加 [[ noreturn ]] --> 如果这应该记录一个致命错误,则很有用
【解决方案3】:

只需将您的参数放在一个元组中,不需要宏。

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

还有这个works*

从技术上讲,你可以写:

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';
}

但是你可能不得不跳过一些障碍来获取参数类型。


* 在链接到的示例中,我使用&lt;experimental/source_location&gt;,因为这是编译器现在接受的。另外,我添加了一些用于打印参数元组的代码。

【讨论】:

  • "这很好用" 你的意思是,除了你必须将值放在一个元组中之外?因此必须处理大量无意义的语法才能实际提取和使用它们以达到预期目的?
  • @NicolBolas: s/a lot of/a bit of/ ;但是 - 请参阅编辑。
  • 这一切都取决于你对他们做什么。在可变参数模板中,将所有值格式化为流非常简单且易于阅读。在您的版本中,两者都不是。这可行,但并不漂亮。
  • @NicolBolas:您可能更喜欢that,但我想说迭代元组/可变参数模板只是风格上的“问题”。
【解决方案4】:
template <typename... Args>
void debug(Args&&... args,
           const std::source_location& loc = std::source_location::current());

“有效”,但需要指定模板参数,因为没有最后一个,所以不可演绎:

debug<int>(42);

Demo

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

  • 使用带有硬编码限制的重载(“处理”可变参数的旧方法):

    // 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

  • source_location 放在首位,不带默认值:

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

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

    Demo

  • 类似于重载,但只是使用元组作为组

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

    Demo

【讨论】:

  • 我最喜欢你的第一个替代方案。虽然它是丑陋的代码,但它使用起来最方便,这也是最重要的。
【解决方案5】:

不是一个很好的解决方案,但是...将可变参数放在 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));

【讨论】:

  • @L.F. - 对不起:也许我误解了:你的意思是你想用模板可变参数函数替换可变参数宏吗?
  • 我原来的问题根本没有意义。我已经更新了我的问题,以使实际问题脱颖而出。忽略可变参数宏。对不起!
  • @L.F. - 我明白了......好吧,我的答案几乎保持不变,但明确调用std::make_tuple() 的需求使它不那么有趣了。
【解决方案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);
}

DEMO

【讨论】:

  • A code-only answer is not high quality。虽然此代码可能有用,但您可以通过说明其工作原理、工作方式、何时使用以及它的局限性来改进它。请edit您的回答包括解释和相关文档的链接。
猜你喜欢
  • 1970-01-01
  • 2021-10-01
  • 1970-01-01
  • 2016-10-05
  • 2019-08-12
  • 2022-01-01
  • 2016-10-06
相关资源
最近更新 更多