Trying to determine a "modern" implementation for the following C-style code:
#define logError(...) log(__FILE__, __LINE__, __VA_ARGS__)
Is is possible to capture this using variadic templates or something similar that does not rely on a #define ?
Desired use case:
logError( "Oh no! An error occurred!" );
Where __FILE__, and __LINE__ are captured under the hood, but reflect the file name and line number of where logError was called from.
Macros are indeed your only choice, at least until std::source_location makes it into the standard and fulfills your wish.
Actually the preprocessor is the only choice when you want to work with line numbers and filenames.
For the compiler it's not possible to use line numbers and filenames as arguments for function calls (or storing them in a variable).
In my company we had the exactly same issue with logging. We ended up with an external script scanning the source files and then building proper functions to call.
Related
Background
I am working on an existing codebase which uses a macro pattern to generate boilerplate methods similar to this:
START_MAP()
MAP_ENTRY(a)
MAP_ENTRY(b)
// .....
MAP_ENTRY(z)
END_MAP()
I am re-implementing the code that these macros generate, but I can not touch this pattern because that would require large refactors. I need to expand this pattern into a macro (which I define) which we will call NEW_IMPLEMENT which is a variadic macro that is called like so: NEW_IMPLEMENT(a, b, ..., z).
Problem
How can I redefine START_MAP, MAP_ENTRY, and END_MAP so that the pattern as it currently exists expands to NEW_IMPLEMENT(a, b, ..., z)?
What I have tried so far
#define NEW_IMPLEMENT(...) ...
#define START_MAP NEW_IMPLEMENT(
#define MAP_ENTRY(x) x,
#define END_MAP )
This throws a preprocessor error, however: error: unterminated argument list invoking macro "NEW_IMPLEMENT"
You can make the pattern expand to NEW_IMPLEMENT(stuff) text, but the resulting macro will not be expanded - C preprocessor does not rescan results from multiple macro expansions together.
You can "join" them, by wrapping everything in another macro call which will force another pass over all the results. And also you need to pass the paren as a token, not literally, so that you don't get unterminated call.
#define CALL(...) __VA_ARGS__
#define PAREN (
#define START_MAP() NEW_IMPLEMENT PAREN
#define END_MAP() )
#define MAP_ENTRY(a) a,
#define NEW_IMPLEMENT(...) "Hello: " #__VA_ARGS__
CALL(
START_MAP()
MAP_ENTRY(a)
MAP_ENTRY(b)
// .....
MAP_ENTRY(z)
END_MAP()
)
But overall, I do not understand. I would prefer to refactor the code with a simple sed 's/START_MAP()/NEW_IMPLEMENT(/; s/MAP_ENTRY(a)/a,/; s/END_MAP()/)/' for code readability and maintainability. I do not think "not touching ancient code" is a good enough reason for making the codebase more convoluted.
I'm writing a logger module, so I would like to print the debug infos like __FILE__ and __LINE__ in c/c++. So I used macro function like:
/// here's just an example
/// function str_format() returns a std::string in format like printf() in c
#define info(str, ...) (cout << str_format(str, ##__VA_ARGS__) << __FILE__ << ":" << __LINE__)
in most cases, it works fine. but 'define' just has more influence than I thought.
to give an example, if a member variable in c++ class needs initialize:
class test{
private:
int info;
public:
test(int a) : info(a) {}
};
this would cause an unexpected error, the compiler would take info as a macro function!
even the header file of logger(which is logger.h) is not included in the class test's file, as long as the third file include them both, and include 'logger.h' before 'test.h' would cause this problem! that's really annoying.
So, is there any way other than #undef (since the test.h has nothing to with logger.h) to solve this problem? or let's say how can I get debug info like FILE without using macro function?
As far as __FILE__ and __LINE__ is concerned, C++20 introduced std::source_location for this. There is no equivalent non-macro based solution in prior C++ standards.
As far as a non-macro logging solution overall, the most common approach involves implementing the logging function as a variadic template with a parameter pack, that ends up formatting the log message, from its parameters.
No, there is no other way. You'll have to either not use file information (really, who cares what your file is called? Most likely you actually care about the call stack at a problem location, which is what exceptions give you) or use better names than info. In fact the convention for macros is to use all caps, like INFO.
I'd like to add information in a crash dump file, in case my application crashes.
Therefore I've created a __try-__except clause:
__try
{
Do_Something();
}
__except (ShowCrashdumpInformation(_T(__FUNCTION__));
Instead of just __FUNCTION__, I'd like to add more information, but how can I do that?
The simpliest way is to use a CString, but this is blocked because of compiler error C2712 (Cannot use __try in functions that require object unwinding).
So, I'd like to use LPCTSTR strings (which are widely used in my application).
As a result it should look like (CString alternative):
CString temp; temp.Format(_T("Do_Something, int=[%d], float=[%f], string=[%s]), iParam, fParam, strParam);
Do anybody have an idea?
Thanks
By far the easiest solution is to simply sidestep the problem. Just forward the exact arguments, not converted, to a (template) function which does the actual writing to file. Since the __catch is not in the template function itself, but one level up the stack, you're safe.
You could use preprocessor macros to "stringify" the standard __LINE__ macro, and rely on the compiler adjacent string-literal concatenation.
Perhaps something like this:
#define STRx(x) #x
#define STR(x) STRx(x)
#define FILE_FUNCTION_LINE (__FILE__ ":" __FUNCTION__ ":" STR(__LINE__))
...
ShowCrashdumpInformation(_T(FILE_FUNCTION_LINE))
As long as you have literal values, you could use the STR macro to "stringify" them and then use adjacent string concatenation.
It's not possible using variables though, only using literal values.
Trying to determine a "modern" implementation for the following C-style code:
#define logError(...) log(__FILE__, __LINE__, __VA_ARGS__)
Is is possible to capture this using variadic templates or something similar that does not rely on a #define ?
Desired use case:
logError( "Oh no! An error occurred!" );
Where __FILE__, and __LINE__ are captured under the hood, but reflect the file name and line number of where logError was called from.
Macros are indeed your only choice, at least until std::source_location makes it into the standard and fulfills your wish.
Actually the preprocessor is the only choice when you want to work with line numbers and filenames.
For the compiler it's not possible to use line numbers and filenames as arguments for function calls (or storing them in a variable).
In my company we had the exactly same issue with logging. We ended up with an external script scanning the source files and then building proper functions to call.
In my VC++ project I use a macro for debugging and logging purpose.
calling:
Logger(LogLevel::Info, "logging started");
macro:
#ifdef DEBUG
#define Logger(level, input) \
{ \
cerr << "[" << level << "] " << input << endl; \
};
#else
#define Logger();
#endif
When compiling this I get the following warning (but it still compiles):
warning C4002: too many actual parameters for macro 'Logger'
I am wondering how the compiler handles this situation.
Are the macro parameters still used for compiling? E.g. will one be able to see the "logging started" string on reverse engeneering?
I am wondering how the compiler handles this situation. Are the macro parameters still used for compiling?
Macros are processed in the pre-processing stage. If the pre-processor is able to deal with the extra arguments used in the usage of the macro, the only thing it can do is drop those parameters in the macro expansion.
E.g. will one be able to see the "logging started" string on reverse engeneering?
No, the code processed by the compiler will not have that line at all.
If you have the option to change those lines of code, I would recommend changing the non-debug definition of the macro to be a noop expansion. E.g.:
#define Logger(level, input) (void)level;
As per the law, a macro function must declare as many parameters as you call it with.
So calling your macro
#define Logger()
as Logger(LogLevel::Info, "logging started") results in an error. MSVC probably allows it because it isn't standard-compliant. There's not much to reason further (which is my answer to the actual question).
You either
#define Logger(unused,unused2)
and leave the replacement part empty or
#define Logger(...)
and suffer the consequences of being able to call with any number of arguments. [Hint: First one is recommended.]