std::source_location doesn't work if you need strings to be types
elbeno.com
elbeno.com
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.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/z1eGc6cM4Could even reuse the explicit template parameter for the verbosity level, like:
log<I>(…); // info
log<D>(…); // debugOr 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.
We are seeing binary sizes increase by 30-50% just from enabling source location, even if we don’t want function_name. Sadly this means we need to turn it back off again and go back to __FILE__ and __LINE__.
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.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__.
#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__,__VA_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.)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.
An elevator I imagine costs big $$$, has no lack of power, and plenty room for electronics.
The main contractor of the elevator unit is a mechanical engineering company, they contract out the control unit to the lowest bidder.
There is even a c++ client-side implementation: https://github.com/Javier-varez/Postform/
...which uses macros for logging ;)
struct fstr { constexpr fstr(const char* s) { for (length = 0; s[length] ; length++) data[length] = s[length]; }
char data[256] = {0};
size_t length = 0;
};template <fstr sz> struct log { log() { std::cout << std::string_view{ sz.data, sz.length } << std::endl; } };
int main() { log<fstr(std::source_location::current().file_name())> _; }
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.
If there's a better (more accurate and neutral) title, preferably using representative language from the article itself, we can change it again.
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.
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.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?
edit: linkage appears to propagate through constexpr. So it depends on how ultimately the original string is generated. I don't know what guarantee the standard gives (cppreference doesn't mention anything and I can't be bothered to look at the standard).
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.
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__.Or at least copy a couple slides from the video.
#include <stdio.h>
#include <array>
#include <source_location>
#include <string_view>
template<size_t N> constexpr auto GetFilename(char const name[]) {
std::array<char, N + 1> a = {};
std::char_traits<char>::copy(&a[0], name, N);
return a;
}
template<auto Value = GetFilename<
std::char_traits<char>::length(std::source_location::current().file_name())
>(std::source_location::current().file_name())>
using SL = std::integral_constant<decltype(Value), Value>;
int main() {
printf("%s\n", &SL<>::value[0]);
} static constexpr std::string_view filename = std::source_location::current().file_name();
static_assert(filename == "whatever_the_name_of_file_is");
static_assert(filename.length() == 28);
will do. Both the filename and filename length is available in the compile-time.See my other comments elsethread.
A good, straightforward solution, using existing tech, is hidden in some leaf comment, or not actually there explicitly. I would guess >>50% of the posters don't know that this is doable. Let me type it out:
void actual_logging_function(std::string_view filename, int line, std::string_view msg)
{
printf("In %.*s.%d: %.*s\n", filename.length(), filename.data(), line, msg.length(), msg.data());
}
#define logging_function(msg) actual_logging_function(std::string_view(__FILE__), __LINE__, (msg))
There is plain C version of this too, just replace std::string_view(__FILE__) by sth like my_string_view(__FILE__, sizeof __FILE - 1). Obviously the length also be passed directly to the logging func.I get it. Macros are terrible. But can we stop coming up with "solutions" that are even more terrible, just on other axes?
And really, the initial "problem" was just that the string length wasn't a compile-time constant. I would venture out and claim that this isn't an actual problem, unless "logging" is confused with "high-performance string formatting". If an application is logging at a rate where a simple "%s" becomes an issue, isn't there something else that is broken? And for context - typically a logging call also has to do other formatting that can't be moved to compile time, such as formatting integers.
Your example can be written in C++ as this:
void logging_function(std::string_view msg, std::source_location l = std::source_location::location()) {
// log here
}
No need for macros, it just work in almost all scenarios.But this solution (and your __FILE__) will add a bunch of strings (representing the file names) into the read only section of the final binary so that they can be available at runtime. Apparently this is undesirable for the embedded scenario the author has in mind. So in this thread we were looking for a way to encode the source_location as a template parameter (which the author claims it can't be done) instead so that it is part of the symbol and can be stripped out.
It is a very niche need, it just turned out to be a fun game to play.
As far as I understood the post, the issue with your above code is that the std::source_location::location() isn't able to use the size (length) of the filename string at compile time (*). While with _FILE_, the size is known -- it's even in its type.
So if you want to play template or constexpr tricks, you probably can do what you have in mind using __FILE__. Just be aware that a (pointer,length) pair (like string_view) or a plain zero-terminated string pointer will have the size information available at runtime but not compile time.
So not saying that I didn't miss anything (I probably did), and not to mean any offense, but maybe you're also one of those >>50% of people that I mentioned? :D
(*) which, as you and I already agree, is probably not really an issue. It's more a "fun" game to play -- the kind of game that a lot of C++ people like to play, and then sell it as "optimal solution" or "zero-cost abstraction".
As shown else thread, you can easily compute the size of std::source_location::current().file_name() at compile time. The only limitation is that neither source_location nor the std::source_location::current().file_name() pointer themselves can be used directly as non-type template parameters. But there are ways around that.
And yes, you can do the same with __FILE__ and __LINE__, but you need to wrap them in a macro for usability. The question is whether there is a solution that doesn't use macros at all (I posted one elsethread).
"foo()::<lambda()#1>"
I remember writing some constexpr expressions to strip the lambda portion (which had a guid). Is there now an easier way?
1 #include <source_location>
2 #include <iostream>
3 int main() {
4 auto c = [](std::source_location loc1 = std::source_location::current()){
5 std::source_location loc2 = std::source_location::current();
6 std::cout << loc1.function_name() << ":" << loc1.line() << "\n";
7 std::cout << loc2.function_name() << ":" << loc2.line() << "\n";
8 };
9 c();
10 return 0;
11 }
As eklitzke said, this results in (using gcc): int main():9
main()::<lambda(std::source_location)>:5Is there a way to extract the undecorated (?) function name? ("main" in this case, without the return type or args)
Maybe a helper class or formatter?
int main():9
auto main()::(anonymous class)::operator()(std::source_location) const:5
so your formatter/helper would need to be compiler-aware, although to extract “main” in both cases I suppose you can just grab everything between the first whitespace and (, although I assume both types would contain specifiers like const where included in the return type. I don’t know what MSVC outputs.If you want to literally capture the name of the lambda and the line number in the lambda you'd create the source_location within the lambda instead.
This post reads to me like complaining that your hammer ruined your screw.
it is quite astounding to me that you're going to try to claim that Intel is using its own chips in an "incorrect" manner.
the presentation might help: https://youtu.be/Dt0vx-7e_B0?si=QLfI5-9LHh5ehb5a&t=326 where specifically the problem they have encountered is that the string constants end up roughly the size of their application code.
And yes, I'd still argue this is poor design, it doesn't matter who designed it. We're all very aware that Intel is not immune to bad design decisions. It's quite astounding to me that you're claiming Itanium was a good design. /eyeroll
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)
Because if so, you could just ... not dereference the pointer, and do object-file magic to make the section not actually mapped.
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.template<std::size_t N> struct fstr: public std::array<char, N + 1> { using std::array<char, N + 1>::array;
constexpr fstr(const char* str) noexcept : std::array<char, N + 1>() {
for (size_t i = 0; i < N; i++) (*this)[i] = str[i];
}
};template<std::size_t N> fstr(const char (&)[N]) noexcept -> fstr<N - 1>;
template <fstr sz> struct log { log() { std::cout << std::string_view{ sz.data(), sz.size() } << std::endl; } };
int main() { log<"Hello, world!"> _; }
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.