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