【问题标题】:How could std::experimental::source_location be implemented?如何实现 std::experimental::source_location ?
【发布时间】:2016-03-28 03:09:24
【问题描述】:

用于库基础的 C++ 扩展,第 2 版 (N4564) 引入了 std::experimental::source_location 类型。

§ 14.1.2 [reflection.src_loc.creation] 说:

static constexpr source_location current() noexcept;

返回:当被函数调用(C++14 §5.2.2)调用时,postfix-expression 是一个(可能带括号的)id-表达式 命名current,返回一个带有实现定义值的source_location。该值应受#line (C++14 §16.4) 的影响,其方式与__LINE____FILE__ 相同。如果以其他方式调用,返回的值是未指定的。

备注:当使用 brace-or-equal-initializer 来初始化非静态数据成员时,任何对 current 的调用都应对应于位置初始化成员的构造函数或聚合初始化。

[ 注意: 当用作默认参数时(C++14 §8.3.6),source_location 的值将是调用current 的位置呼叫站点。 — 尾注 ]

如果我理解正确,那么该功能就是打算这样使用的。

#include <experimental/source_location>  // I don't actually have this header
#include <iostream>
#include <string>
#include <utility>

struct my_exception
{

  std::string message {};
  std::experimental::source_location location {};

  my_exception(std::string msg,
               std::experimental::source_location loc = std::experimental::source_location::current()) :
    message {std::move(msg)},
    location {std::move(loc)}
  {
  }

};

int
do_stuff(const int a, const int b)
{
  if (a > b)
    throw my_exception {"a > b"};  // line 25 of file main.cxx
  return b - a;
}

int
main()
{
  try
    {
      std::cout << do_stuff(2, 1) << "\n";
    }
  catch (const my_exception& e)
    {
      std::cerr << e.location.file_name() << ":" << e.location.line() << ": "
                << "error: " << e.message << "\n";
    }
}

预期输出:

main.cxx:25: error: a > b

如果没有std::experimental::source_location,我们可能会使用辅助宏THROW_WITH_SOURCE_LOCATION,它在内部使用__FILE____LINE__ 宏来正确初始化异常对象。

我想知道一个库如何实现std::experimental::source_location。除非我完全忽略了这一点,否则如果没有特殊的编译器支持,这样做是不可能的。但是需要什么样的魔法编译器功能才能使这项工作?它可以与为std::initializer_list 部署的技巧相媲美吗?是否有此功能的实验性实现可供查看?我检查了the SVN sources for GCC,但还没有找到任何东西。

【问题讨论】:

  • 编译器魔法,根据std::type_info
  • @RichardHodges typeid 可以实现为一个相当直接的函数。对于非多态类型,编译器已经知道答案可以直接插入字符串文字,对于多态类型,它必须在运行时生成简单的代码来遵循vptr。我看不出source_location 的实现如何同样简单。但如果你知道,那么这正是我正在寻找的答案。
  • 但是在没有编译器支持的情况下,如何仅使用库获取类型的名称?在 type_traits 中还有许多需要编译器魔法的助手。例如is_class/enum/union.
  • 它要做的不仅仅是生成简单的代码——它还必须生成数据结构。尽管如此,编译器还是可以访问行号和文件名(这些已经被注入到源代码中,这样__FILE____LINE__ 就可以工作了。std::source_location 所做的只是将与用户代码的关系形式化。就像编译器/标准库适用于std::initializer_list(另一个“魔术”类)
  • @5gon12eder 当然是。否则std::cout &lt;&lt; __LINE__ &lt;&lt; std::endl 将如何工作?请记住,在扩展预处理器指令(例如#include)之后,所有实际的行号都发生了变化,但我们的代码报告了我们期望的行号。预处理器和编译器阶段之间存在松散但密切的关系。

标签: c++ reflection c++17 fundamentals-ts std-source-location


【解决方案1】:

实现这一点需要编译器的支持。例如,with gcc,您可以使用 built-ins functions 之类的

   int __builtin_LINE()

这个函数相当于预处理器__LINE__宏,返回调用内置函数的行号。在函数F 的C++ 默认参数中,它获取对F 的调用的行号。

   const char * __builtin_FUNCTION()

这个函数相当于预处理器__FUNCTION__宏,返回调用内置函数的函数名。

   const char * __builtin_FILE()

此函数等效于预处理器__FILE__ 宏并返回调用内置函数的文件名。在函数F 的C++ 默认参数中,它获取调用的文件名到F

【讨论】:

  • 酷,这正是我一直在寻找的那种功能。我不知道它们已经在 GCC 中实现了。尤其是默认参数的语义非常显着(这正是source_location 所要求的)。在我看来,libstdc++ 无需任何额外的魔法就可以逃脱。
  • 好吧,除了链接电子邮件中提到的限制......
  • @5gon12eder 是的,电子邮件链中的bugzilla link 提到了使用现有内置插件的一些限制,但我猜最终实现将使用非常相似的东西。
猜你喜欢
  • 2017-11-29
  • 2019-03-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-02
  • 2017-04-10
  • 2016-12-15
相关资源
最近更新 更多