[HN Gopher] std::source_location Is Broken
       ___________________________________________________________________
        
       std::source_location Is Broken
        
       Author : jandeboevrie
       Score  : 53 points
       Date   : 2023-11-16 17:03 UTC (5 hours ago)
        
 (HTM) web link (www.elbeno.com)
 (TXT) w3m dump (www.elbeno.com)
        
       | swatcoder wrote:
       | Broken is a strong word.
       | 
       | There are a lot of language features that aren't suitable to
       | specific contexts like embedded or realtime.
       | 
       | It'd be interesting to see if this concern was raised during
       | discussion of source_location and whether a rationale was
       | established, but I wouldn't go so far as to say it's broken when
       | it just happens not to be suitable to a specific, narrow
       | implementation context.
        
         | lainga wrote:
         | Everyone knows that the best way to get something done on the
         | Internet is with an inflammatory title and a potentially
         | incorrect contention.
        
           | MaxBarraclough wrote:
           | Cunningham's Law: _The best way to get the right answer on
           | the internet is not to ask a question; it 's to post the
           | wrong answer._
        
       | snickerbockers wrote:
       | >But of course this is a macro, and means that your
       | logging/assertion code has to be a macro.
       | 
       | No, only the code that references __FILE__ needs to be a macro.
       | Usually what i see is a logging function that takes line and file
       | as arguments, and then a macro that calls the function with
       | __FILE__ and __LINE__.
        
         | ninepoints wrote:
         | If you expose a macro that then calls your function, what's the
         | difference between what OP is saying?
        
           | jacoblambda wrote:
           | If it's a macro, then the preprocessor expands the macro at
           | the callsite (which then expands the __FILE__ and __LINE__
           | macros in that location).
           | 
           | If you use a function, then the __FILE__ and __LINE__ expand
           | inside that function and point to the location inside the
           | function rather than the callsite of the function.
        
           | tom_ wrote:
           | OP seems to be saying that the logging code has to be in a
           | macro. But it doesn't: it can be in a function that's called
           | by a macro. It's the difference between having the logging
           | code in a macro:                   #define LOG(...)\
           | do{\                 if(g_logging_enabled){\
           | printf("%s:%d: ",__FILE__,__LINE__);\
           | printf(__VA_ARGS__);\                 }\
           | }while(0)
           | 
           | And having the logging code in a function called by a macro:
           | extern void Log(const char*file,int line,const char*fmt,...);
           | #define LOG(...) (g_logging_enabled?Log(__FILE__,__LINE__,__V
           | A_ARGS__):(void)0))
           | 
           | With the Log function being along these lines:
           | void Log(const char*file,int line,const char*fmt,...){
           | printf("%s:%d: ",file,line);             va_list v;
           | va_start(v,fmt);             vprintf(fmt,v);
           | va_end(v);         }
           | 
           | (This logging code is deliberately simple; in a realistic
           | system, this would be pages of junk dealing with all manner
           | of log categories, log levels, multiple enable flags and
           | their overrides, log target handling, and whatnot. Absolutely
           | the sort of thing that is 0 fun to keep track of in a macro,
           | even before you consider the fact that it's a ton of code to
           | be pasting everywhere you want to log a string.)
        
       | nemetroid wrote:
       | If the blog post author showed us the code they want to write
       | (but doesn't work), it might be easier to understand what they
       | mean.
        
         | Joker_vD wrote:
         | I imagine they want something like this [0] but only with even
         | less string pointers, using e.g. hashes or globally assigned
         | numbers instead.
         | 
         | [0] https://www.embeddedrelated.com/showarticle/518.php
        
         | ReleaseCandidat wrote:
         | It's this problem:
         | https://stackoverflow.com/questions/52977593/stdexperimental...
        
         | vlovich123 wrote:
         | What do you mean? They do. They want to replace the name of the
         | file with a number at compile time so that the logs are tiny.
         | The way they do that now is that they pass __FILE__ to a char
         | parameter pack expansion to generate a unique type that erases
         | to nothing but a number at compile time and extract the unique
         | id of that type using nm but that doesn't work with
         | source_location because it has char* as the type.
         | 
         | I wonder if there's some kind of workaround where you wrap
         | source_location so that it outputs the hash of the file name
         | using a know consteval hashing function. Then you have your
         | unique type (just assign the hash value to your type) at the
         | cost of some extra overhead of needing to hash every filename
         | in your build.
        
           | nemetroid wrote:
           | That's a prose description, not code.
        
             | nottorp wrote:
             | I thought one of the basic skills of programming is to turn
             | a prose description into code...
        
               | nemetroid wrote:
               | I don't think I've claimed otherwise.
        
               | Dylan16807 wrote:
               | Doing that takes a lot of time. It would help the article
               | to spell it out somewhat better.
               | 
               | Or at least copy a couple slides from the video.
        
               | tom_ wrote:
               | And one of the basic problems of programming is that the
               | resulting code is almost certainly wrong. Which is why
               | it's always safest to look at the specific code in
               | question, rather than a description of what it's supposed
               | to do.
        
           | ynik wrote:
           | But it's quite unclear what they are currently doing.
           | Literally translating what you are describing:
           | template<char... FILE>        struct magic { };
           | magic<__FILE__> m;
           | 
           | This does not compile with gcc, and std::source_location
           | fails to work for exactly the same reason. I don't see any
           | additional problems with std::source_location that don't
           | equally apply to __FILE__.
        
         | senkora wrote:
         | They did link an hour long conference talk (which I haven't
         | watched), that I assume explains in more detail the kind of
         | logging system that they're working with:
         | https://www.youtube.com/watch?v=Dt0vx-7e_B0
         | 
         | I think that basically they want a class template magic_functor
         | that can be invoked as below
         | magic_functor<std::source_location::file_name()>::type
         | 
         | And then they could get a compile-time number corresponding to
         | the file_name by doing:                   using T =
         | magic_functor<std::source_location::file_name()>::type;
         | int x = my_logging_lib_string_to_number<T>();
         | 
         | But unfortunately it simply doesn't work, because magic_functor
         | is unwritable, because a `const char *` doesn't remember its
         | size like a `const char[]` does.
        
           | ynik wrote:
           | If the `const char*` is a compile-time value, you can get its
           | size via `std::char_traits<char>::length()`.
           | 
           | Here I could get
           | `magic_functor<std::source_location::file_name()>::type` to
           | work on MSVC: https://godbolt.org/z/Ph8dd78fa gcc doesn't
           | accept this code, but that seems more like an implementation
           | problem than a specification problem?
        
           | jeffreygoesto wrote:
           | I liked this better, it uses some macros but it's easily
           | readable and compresses the logs nicely.
           | 
           | https://youtu.be/FyJI4Z6jD4w
        
       | Conscat wrote:
       | That's an interesting problem. I'm still realizing how different
       | embedded software constraints are from the desktop.
        
         | calamari4065 wrote:
         | I don't think this post is actually that representative of the
         | field.
         | 
         | Fretting over the binary size of string constants is not
         | something I've _ever_ seen. Generally if you don 't have enough
         | flash to store strings, you also don't have a use for strings.
         | You won't be logging to external memory or to serial. If you
         | have that little flash, you generally also have _very_ few CPU
         | cycles. Logging of any sort would take up a huge percentage of
         | your CPU cycles and you won 't have anything left to run the
         | application. Not to mention the RAM, which is usually much much
         | smaller than flash.
         | 
         | The problem posed in TFA isn't really a problem. If you need
         | continual logging, you must use a chip with more CPU, which
         | almost always means more memory. If you have a chip with 512B
         | of flash and 128B of RAM, you don't need logging.
         | 
         | Exceptions apply, of course, but this is a case of wrong tool
         | for the wrong problem.
        
       | elteto wrote:
       | > You need to know the size at compile time.
       | std::source_location::file_name() gives you a const char* - you
       | don't know the size of the string. And because it has to be a
       | function argument, it can't be constexpr.
       | 
       | At least this part is not entirely correct, the following
       | compiles in C++20:                 consteval std::string_view
       | filename(const std::source_location &loc =
       | std::source_location::current()) {         return
       | loc.file_name();       }
       | 
       | See [0]. I don't know how to go from here to a type, as the
       | author wants, but I'll venture that there's some ungodly template
       | metaprogramming hack that will get you there.
       | 
       | [0] https://godbolt.org/z/K6ejTr9cn
        
         | lionkor wrote:
         | you can also do compile time strlen(), if you are ok with goofy
         | variadic templates
        
           | UebVar wrote:
           | Implementing a constexpr strlen() is trivial and I looks just
           | like it's from your ancient C textbook, save for the
           | "constexpr" keyword. No goofyness involved.
           | 
           | Or you use what the C++ standard library has to offer.
           | 
           | std::string_view(ptr).length(); or std::string(ptr).length();
           | or std::char_traits<char>::length(ptr); all work at compile
           | time.
        
         | gpderetta wrote:
         | So, one issue is that source_location is not a valid non-type
         | template parameter as it is not a structural type. But you can
         | trivially make one yourself if you chose an upper bound on the
         | filename (and function name) size.
         | 
         | The second issue is that it seems that default values for non-
         | type template parameters are not evaluated at the instantiation
         | location, but at the definition location, so you need to make
         | the template parameter explicit. This is the best I could come
         | up with:                  log<o>("hello", "world")  // at
         | example.cpp:30
         | 
         | which prints[1]:                  app/example.cpp:30: hello
         | world
         | 
         | [1] https://godbolt.org/z/z1eGc6cM4
        
       | o11c wrote:
       | Hmm, is there a way to force particular string constants to be
       | written to particular ELF sections?
       | 
       | Because if so, you could just ... not dereference the pointer,
       | and do object-file magic to make the section not actually mapped.
        
       | ynik wrote:
       | `std::source_location` does not have to be a function argument.
       | 
       | It can also be used in an NSDMI, in which case it ends up being
       | instantiated whenever the NSDMI is used, i.e. at every aggregate
       | initialization site.
       | 
       | https://godbolt.org/z/v5rhjqdbY
       | 
       | Of course, that doesn't really allow you to do anything that you
       | couldn't already do in the with a constexpr function (see below).
       | In particular, such a struct can be used as a non-type template
       | parameter, but a `template<NSDMI sloc = NSDMI{}> void foo();`
       | will merely report the line where the template is defined, not
       | where `foo` is called.                   // Results in a compile-
       | time constant, so can be used everywhere where __LINE__ could be
       | used.         constexpr int line(std::source_location loc =
       | std::source_location::current()) {             return loc.line();
       | }
       | 
       | So std::source_location can fully replace __FILE__/__LINE__, but
       | it cannot always replace the macro where __FILE__/__LINE__ were
       | being used.
        
         | foota wrote:
         | Huh, being able to see where a template was instantiated is
         | niche but kind of interesting.
        
       | beached_whale wrote:
       | The issue with source_location is that it cannot be used in a
       | macro, what I generally want is something like
       | stack_trace::parent( ) so that assert like macros can report the
       | location of their caller and error, not the location of the macro
        
         | kevingadd wrote:
         | something like stack_trace::parent would be hard to do in a
         | modern toolchain, since we now have people deploying code to
         | environments like WebAssembly that prohibit stackwalking
         | 
         | In C#/.NET's case, even though stackwalking is available in the
         | API, it is preferred to push the location info in from outside
         | and there are mechanisms to automate it (CallerFilePath, etc)
        
       | Tempest1981 wrote:
       | Does anyone know what std::source_location::function_name()
       | returns inside a lambda? Is it the outer function name, or
       | something like this:
       | 
       | "foo()::<lambda()#1>"
       | 
       | I remember writing some constexpr expressions to strip the lambda
       | portion (which had a guid). Is there now an easier way?
        
       | tomasGiden wrote:
       | Regarding the problem of strings in embedded, I once did a pretty
       | nifty thing in a logging framework I built for an embedded system
       | in an elevator (Cortex M0 I think it was) with very little flash
       | for the binary and for logging. I had a logging macro which took
       | in a string to log together with some arguments (like printf).
       | The macro expanded to add an attribute to the string constant to
       | put it in a special section I created with a linker script. Then
       | in the macro what it actually logged was the memory offset in the
       | section together with the arguments. So that way the log was
       | extremely slim. As an extra bonus, I then stripped the special
       | section with the strings from the binary and had an offline
       | script translate the logged memory offsets and attributes to
       | strings.
        
         | thrtythreeforty wrote:
         | Google's embedded library Pigweed [1] industrializes exactly
         | this approach and has some unholy macro nonsense to make the
         | string hashing work in pure C as well.
         | 
         | [1]: https://pigweed.dev/pw_log_tokenized/
        
         | dale_glass wrote:
         | Out of curiosity, why would you have to be so efficient in an
         | elevator?
         | 
         | An elevator I imagine costs big $$$, has no lack of power, and
         | plenty room for electronics.
        
       | mknejp wrote:
       | This is doable, but it doesn't have great ergonomics. Here
       | https://godbolt.org/z/1z4qP8esb you can find a general-purpose
       | function to_static_array(), which can turn any dynamically sized
       | input range into a std::array at compile time. The string is
       | stored as a std::array NTTP and not contained in the binary.
       | 
       | But there is a big catch: the range _must_ be returned from a
       | constexpr function. So to get the correct source_location object
       | you always need to spell out the entire `to_static_array <100>([]
       | { return current_file_name(); }` expression where you need it.
       | 
       | So while this does work for source_location::file_name() it
       | doesn't for source_location::function_name() as that will always
       | result in the name of the lambda.
       | 
       | So it's only a partial solution. And has terrible ergonomics for
       | this particular use case. so I don't think we have completely
       | eliminated the need for macros just yet.
       | 
       | But other than that it's a great tool if you want to do some
       | constexpr computations with std::vector or std::string and then
       | turn the result into a constant-sized compile-time array,
       | optionally baking it into the binary.
        
       | James_K wrote:
       | string_constant<'H', 'e', 'l', 'l', 'o'>
       | 
       | Sometimes I see C++ code, and I feel deeply sad for the people
       | that are subjected to it.
        
       | ape4 wrote:
       | A little video of a guy playing with it
       | https://www.youtube.com/watch?v=TAS85xmNDEc
        
       ___________________________________________________________________
       (page generated 2023-11-16 23:00 UTC)