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