2

I have a function whose behavior may need to be modified based on the file it is called from (e.g., to increase debugging trace output). This modification would need to be done without recompiling (e.g., by editing a configuration file or changing an environment variable).

Just as an example that would fill this need, I could write Function() as:

FunctionModifiable( const char* pszFile, int i );

Then make a macro thusly:

#define Function( i ) FunctionModifiable( __FILE__, (i) )

And FunctionModifiable() would have the duty to check pszFile in say an unordered_set<> that was populated during initialization to see if special functionality need be activated.

However, the overhead of that search is a minus (this is high-performance software and the function is called a huge number of times), and there is some per-file data that would need to be cached in this situation. We can eliminate the search, and have get storage for the cached info, by passing in not __FILE__ but a pointer to a helper object. This object needs the filename so that, when it undergoes one-off initialization, it can consult config or environment variables or what have you to know whether it needs special handling.

FunctionHelperObject fho( __FILE__ );

#define Function( i ) FunctionModifiable( &fho, (i) )  // C-style solution
#define Function( i ) fho.MethodModifiable( (i) )        // C++-style solution

OK, now say I want to avoid users having to define that fho in every file. (Inter alia, we can't re-write all existing files calling Function(), though say we're willing to recompile them).

The idea I had was the unusual step of putting a variable definition in the header file, so that any program including the header for Function() would get a FunctionHelperObject fho( __FILE__ ) for free. (Such definition would be #pragma once or guarded by a preprocessor variable.

The problem is that __FILE__ at that point would be the name of the header, not of the top-level compilation unit. If there was a __CFILE__ symbol, that would be the solution, but there's not.

Ultimately the best I can think of has shortcomings: 1) the "modifiable" aspect would only be available in source code explicitly written to use it, and 2) you'd have to do that explicit writing, and 3) starting to get a little complicated. In code you want to add the ability to modify behavior to you'd write USE_MODIFIABLE_FUNCTION somewhere after including the header in question. That'd be a macro that creates the FunctionHelperObject above, this time in the right "file" so __FILE__ would have the required value, and furthermore defines a macro as seen above that would mask the non-customizable function Function() with one of the two macros seen above. In short: the previous example, plus

#define USE_MODIFIABLE_FUNCTION FunctionHelperObject fho( __FILE__ );\n#define Function( i ) fho.MethodModifiable( (i) )

Code written without USE_MODIFIABLE_FUNCTION would simply call the uncustomizable Function() the normal way.

But surely this is some other accepted, portable way to provide this behavior? Although I've talked exclusively about the C preprocessor, is there perhaps any C++ template magic or any other approach that would work?

Swiss Frank
  • 1,985
  • 15
  • 33
  • Your use case is unclear to me. If the behavior of the function depends on the caller, then there appear to be several easier options to achieve that : 1) move the differing code to the caller, 2) pass a function pointer for the differing code, 3) pass specific parameters that enable/activate/facilitate the differing behavior. None of this requires knowing the source file name. – Sander De Dycker Mar 02 '20 at 07:42
  • the use case is to be able to do so 1) without modifying code, 2) controlling behavior by environment variable, config file, or some similar means. If you want specifics, imagine that Function() would output ridiculous amounts of logging if you turned on logging no matter where it was called from, but we have a small number of places we want to turn logging on from. But we also don't know specifically what those places are when we're building the program. And there's substantial code we don't have mandate to modify but can recompile as is. – Swiss Frank Mar 02 '20 at 08:42
  • 1
    I can't fully visualize the problem, but your description of changing behavior scenarios at run-time makes me think of the [Strategy pattern](https://en.wikipedia.org/wiki/Strategy_pattern). – Phil Brubaker Mar 02 '20 at 09:20
  • So, you have full control over the implementation of `Function`, but cannot modify the calling code (except to rebuild it with a new version of the header file that provides `Function`) ? – Sander De Dycker Mar 02 '20 at 09:21
  • Yes, Sander, that's the problem. – Swiss Frank Mar 03 '20 at 01:58

2 Answers2

2

Cache the result.

// in the header with Function macro
static FunctionHelperObject functionhelper;
static inline void FunctionModifiableInterface(const char *file, int i) {
    static initialized = 0;
    if (initialized == 0) {
        initialized = 1;
        functionhelper = FunctionHelperObject(file);
    }
    FunctionModifiable(&functionhelper, i);
}
#define Function(i) FunctionModifiableInterface(__FILE__, (i))

You can't predict where the user would want to call you Function(i), so you can't predict the value of __FILE__. Just initialize it on the first call, which also is great, because you will not initialize it if Function is not called. You could do the same initialized check inside FunctionHelperObject constructor.

The really cool and hard to do trick is to modify your build system to allow you to pass a macro with the filename of compiled C file. Because build systems compile one C file at a time, it is possible (and it's a shame compilers doesn't do that by themselves). If you are using cmake with make backend (or really just make by itself), you could do something like this:

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D__MY_FILE__='\"$(notdir $(abspath $<))\"'")

And then use FunctionHelperObject fho(__MY_FILE__) like you wanted to, because __MY_FILE__ depends only on the output filename from make.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • Thanks! I didn't quite think of either of these solutions. As for the makefile it should be trivial, unless the quotes are a problem: a .c.o rule adding -DCFILE=$< should work. As for the object, the "initialized" flag can be within the object, for instance if ( !functionhelper->Initialized() ) {Initialize(__FILE__);} which can then go do one-off caching of data such querying environment variables to see if special lagging is needed etc. – Swiss Frank Mar 04 '20 at 00:22
1

One option is something like this (rough example, keeping the C++ syntax from the OP) :

#define Function(i) do { \
    static FunctionHelperObject obj(__FILE__); \
    FunctionModifiable(&obj, (i)); \
} while (0)

Note the above will have to be modified to accommodate for a return value if the function has one.

Also : an alternative to __FILE__ might be __func__ if that fits better with your needs.

Sander De Dycker
  • 16,053
  • 1
  • 35
  • 40
  • Thanks Sander. I gave the "answer" to KamilCuk as they also had a C pre-processor hack as well :-D Your solution almost as written would be good if Function() were only called in one place per file but for us it's many places, and we don't need a helper object for each call, so in that respect KamilCuk's idea is a better fit. If you wanted to customize per-call then obj() could take a __LINE__ too. Yes, __func__ would be a good alternative for some projects but per-file seems like the best fit for us. – Swiss Frank Mar 04 '20 at 00:27
  • 1
    @SwissFrank : KamilCuk's solution is indeed better - I hadn't considered using a `static inline` function. – Sander De Dycker Mar 04 '20 at 07:17
  • oh I wouldn't say better, just different; yours scales to per-line and per-func possibly a bit more efficiently than his. – Swiss Frank Mar 05 '20 at 08:05