How could std::experimental::source_location be implemented?
Asked Answered
D

1

16

C++ Extensions for Library Fundamentals, Version 2 (N4564) introduces the type std::experimental::source_location.

§ 14.1.2 [reflection.src_loc.creation] says:

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 ]

If I understand correctly, then the feature is intended to be used like this.

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

Expected output:

main.cxx:25: error: a > b

Without std::experimental::source_location, we might have used a helper macro THROW_WITH_SOURCE_LOCATION that internally makes use of the __FILE__ and __LINE__ macros to initialize the exception object properly.

I was wondering how a library could implement std::experimental::source_location. Unless I'm completely missing the point, doing so is not possible without special compiler support. But what kind of magic compiler features would be needed to make this work? Would it be comparable to the trick deployed for std::initializer_list? Is there any experimental implementation of this feature available to look at? I have checked the SVN sources for GCC but didn't find anything yet.

Dania answered 21/12, 2015 at 23:8 Comment(6)
compiler magic, as per std::type_infoAnglicism
@RichardHodges typeid can be implemented as a fairly straight-forward function. For non-polymorphic types, the compiler already knows the answer can can directly insert a string literal, for polymorphic types, it has to generate simple code to follow the vptr at run-time. I don't see how source_location would be equally simple to implement. But if you know, then that's exactly the kind of answer I'm looking for.Dania
But how you are gonna get name of the type with library only without compiler support? Also there are many helpers requiring compiler magic in type_traits. For exampe is_class/enum/union.Interfertile
It has to do more than generate simple code - it has to generate the data structures. Nevertheless, the compiler has access to line numbers and file names (these are injected into the source already so that such things as __FILE__ and __LINE__ work. All std::source_location will do is formalise the relationship with user code. Much as the compiler/standard library does for std::initializer_list (another 'magic' class)Anglicism
@Interfertile Yes, I know, but these intrinsics all operate on the type system which the compiler already has intimate knowledge of. The source location seems to be a different thing. Is this kind of information tracked by today's compilers?Dania
@Dania of course it is. otherwise how would std::cout << __LINE__ << std::endl work? Remember that after expanding preprocessor directives (such as #include) all the actual line numbers has shifted, and yet our code reports the ones we expect. There is a loose but intimate relationship between the preprocessor and the compiler stages.Anglicism
D
17

Implementing this will require support from the compiler. For instance, with gcc, you could possibly use built-ins functions like

   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.

Draughty answered 21/12, 2015 at 23:23 Comment(3)
Cool, that's exactly the kind of features I've been looking for. I didn't know they were already implemented in GCC. Especially the semantics for default-arguments are remarkable (and exactly what source_location requires). It seems to me then that libstdc++ could get away without any additional magic.Dania
Well, except for the limitations mentioned in the linked e-mail…Dania
@Dania Right, the bugzilla link in the email chain mentions some of the limitations to using the existing built-ins but I'm guessing the final implementation will use something quite similar.Draughty

© 2022 - 2024 — McMap. All rights reserved.