Is there any way at all in C++ to declare a static variable while passing it to a function? I'm looking to use a macro to expand to an expression passed to a function. The expression needs to declare and initialize a static variable on that particular line based on the filename and line number using __FILE__
and __LINE__
.
int foo(int b)
{
int c = b + 2;
return c;
}
int main()
{
int a = 3;
a = foo(static int h = 2); //<---- see this!
cout << a;
return 0;
}
The problem I'm trying to solve is getting the filename and line number with the __FILE__
and __LINE__
macros provided by the preprocessor and then creating a lookup table with integer keys leading to the __FILE__
, __LINE__
pairs. For example, the key 89 may map to file foo.cpp, line 20. To get this to work I'm trying to use local static variables so that they are initialized only once per line execution. The static variable will be initialized by calling a function that calculates the integer key and adds an entry to the lookup table if it is not already there.
Right now the program uses enumerated values sent as message class variables to send exception information. I'm writing a macro to wrap the enumerated variables into a new class object: WRAPPER_MACRO(old_enumerated_value)
will expand to NewClass(old_enumerated_value, key_value)
. If I add the static variable key_value's declaration right before this, it should work. The problem is that in most places in the code, the old enumerated value is passed as an argument to a function. So the problem becomes declaring and initializing the static variable somehow with the macro, while keeping the existing function calls.
Ok, I'm going to post the larger problem...
Here is a sample line in the program:
ingress->setReleaseCause(CauseCode::CALL_REJECTED);
This line appears all over the code. We'd like to pinpoint the error to this particular line. What I'd like to do is insert a macro (name changed):
ingress->setReleaseCause(OUR_RELEASE_CAUSE(CauseCode::CALL_REJECTED));
which will change the line to
ingress->setReleaseCause(NewCauseCode(CauseCode::CALL_REJECTED, key_value));
If we can send __FILE__
and __LINE__
, then we'd just replace key_value with __FILE__
and __LINE__
.
If we weren't inside this function call, we could just make another macro that takes as "parameters", the NewCauseCode variable to update and a CauseCode::Value enumerated value and expands out to two lines, the first being a local static variable declaration that calculates the key value and the second line being the update function to give the new class object the key value:
#define SET_OUR_RELEASE_CAUSE(NEW_RELEASE_CAUSE_VAR, CAUSE_CODE_VAL) \
{ \
static int key = getKey(...based on _FILE_ and _LINE_ lookup table); \
setNewReleaseCause(NEW_RELEASE_CAUSE_VAR, CAUSE_CODE_VAL, key); \
}
(Here, key would only be initialized once per line as it is local static.)
Basically, we'd like the keys calculated once per line. One promising solution I saw yesterday was template metaprogramming - which is Turing complete and is done at compile time. The syntax looks very dense though.
The keys can be calculated at start up for this problem.
Update:
I'm trying a solution with two lines, where the macro simply gets the key. The programmer will then use the key in a Call to the new class's constructor.
The reason for the key... we can't use a string because we will be sending the info to a different process and a string would have to be serialized which is a performance hit versus an int.
Just had another idea... I'm going to try sending a function call as the key_value argument, but make the funcion a one-line inline function that calls another function to get the key. This might just work.
Well, it looks like the preprocessor will still run before the compiler inlines the function.
Update 2
Well I finally figured out a solution using constants, the preprocessor's concatenate function and a global variable. Woot! We decided that two lines would be okay. Here's the idea:
#define OUR_RELEASE_CAUSE(INT_VAL) \
myClass(INT_VAL, staticVar)
#define GET_RELEASE_KEY() \
{ \
const int constVar##__LINE__ = __LINE__; staticVar = constVar##__LINE__; \
}
class myClass
{
public:
int a;
int b;
myClass(int a_, int b_)
{
a = a_;
b = b_;
}
int getA()
{
return a;
}
int getB()
{
return b;
}
};
static int staticVar;
int main()
{
GET_RELEASE_KEY();
myClass myVar = OUR_RELEASE_CAUSE(8);
cout << myVar.getB() << endl;
GET_RELEASE_KEY();
myClass myVar2 = OUR_RELEASE_CAUSE(8);
cout << myVar2.getB() << endl;
return 0;
}
Update 3
It looks like this will be a problem because we can't initialize constants with function calls. However, it also appears that we can eliminate the const qualifier. This may make the function be called each time. Perhaps changing const to static is the solution. Yes, this appears to do the trick:
#define OUR_RELEASE_CAUSE(INT_VAL) \
myClass(INT_VAL, glbVar)
// the following static initialization executes at most once per expansion!
#define GET_RELEASE_KEY() \
{ \
static int statVar##__LINE__ = getKey(__LINE__); glbVar = statVar##__LINE__;\
}
int getKey(int i)
{
return i + 1;
}
class myClass
{
public:
int a;
int b;
myClass(int a_, int b_)
{
a = a_;
b = b_;
}
int getA() {return a;}
int getB() {return b;}
};
static int globalVar;
int main()
{
GET_RELEASE_KEY();
myClass myVar = OUR_RELEASE_CAUSE(8);
cout << myVar.getB() << endl;
GET_RELEASE_KEY();
myClass myVar2 = OUR_RELEASE_CAUSE(8);
cout << myVar2.getB() << endl;
GET_RELEASE_KEY();
myClass myVar3 = OUR_RELEASE_CAUSE(8);
cout << myVar3.getB() << endl;
GET_RELEASE_KEY();
myClass myVar4 = OUR_RELEASE_CAUSE(8);
cout << myVar4.getB() << endl;
return 0;
}