std::experimental::source_location如何实现?

16

C++扩展库基础第二版 (N4564) 引入了类型 std::experimental::source_location

§ 14.1.2 [reflection.src_loc.creation] 规定:

static constexpr source_location current() noexcept;

Returns: When invoked by a function call (C++14 § 5.2.2) whose postfix-expression is a (possibly parenthesized) id-expression naming current, returns a source_location with an implementation-defined value. The value should be affected by #line (C++14 § 16.4) in the same manner as for __LINE__ and __FILE__. If invoked in some other way, the value returned is unspecified.

Remarks: When a brace-or-equal-initializer is used to initialize a non-static data member, any calls to current should correspond to the location of the constructor or aggregate initialization that initializes the member.

[ Note: When used as a default argument (C++14 § 8.3.6), the value of the source_location will be the location of the call to current at the call site. — end note ]

如果我理解正确,那么此功能的使用意图是这样的。
#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 部署的技巧相当?是否有任何实验性的实现可供查看?我已经检查了 GCC 的 SVN 源代码,但尚未找到任何内容。


8
编译器魔法,按照std::type_info的说法。 - Richard Hodges
@RichardHodges typeid 可以作为一个相当直接的函数实现。对于非多态类型,编译器已经知道答案并可以直接插入一个字符串字面量,对于多态类型,它必须在运行时生成简单的代码来跟随 vptr。我不知道 source_location 如何实现同样简单。但是如果你知道,那么这正是我正在寻找的答案类型。 - 5gon12eder
但是如果没有编译器支持,你怎么只用库就能获取类型的名称呢?此外,在type_traits中有许多需要编译器魔法的辅助函数,例如is_class/enum/union - Revolver_Ocelot
它不仅要生成简单的代码,还要生成数据结构。然而,编译器可以访问行号和文件名(这些已经注入到源代码中,以便像__FILE____LINE__这样的东西能够工作)。所有std::source_location要做的就是规范与用户代码的关系。就像编译器/标准库为std::initializer_list(另一个“神奇”的类)所做的一样。 - Richard Hodges
@Revolver_Ocelot 是的,我知道,但这些内置函数都是在编译器已经熟悉的类型系统上操作的。源代码位置似乎是另一回事。今天的编译器是否跟踪此类信息? - 5gon12eder
1
@5gon12eder 当然是这样的。否则 std::cout << __LINE__ << std::endl 怎么能工作呢?请记住,在展开预处理器指令(如 #include)后,所有实际行号都已经发生了偏移,但我们的代码报告的是我们期望的行号。预处理器和编译器阶段之间存在着松散但密切的关系。 - Richard Hodges
1个回答

17
实现这一点需要编译器的支持。例如,使用gcc, 您可能可以使用内置函数,如
   int __builtin_LINE()

This function is the equivalent to the preprocessor __LINE__ macro and returns the line number of the invocation of the built-in. In a C++ default argument for a function F, it gets the line number of the call to F.

   const char * __builtin_FUNCTION()

This function is the equivalent to the preprocessor __FUNCTION__ macro and returns the function name the invocation of the built-in is in.

   const char * __builtin_FILE()

This function is the equivalent to the preprocessor __FILE__ macro and returns the file name the invocation of the built-in is in. In a C++ default argument for a function F, it gets the file name of the call to F.


很棒,这正是我一直在寻找的功能。我不知道它们已经在GCC中实现了。特别是默认参数的语义非常出色(也正是source_location所需的)。那么在我看来,libstdc++似乎可以摆脱任何额外的魔法。 - 5gon12eder
除了链接电子邮件中提到的限制之外... - 5gon12eder
@5gon12eder 对的,在电子邮件链中的bugzilla链接提到了使用现有内置函数的一些限制,但我猜最终的实现将使用类似的东西。 - Praetorian

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