0

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;
    }
Community
  • 1
  • 1
MDC
  • 11
  • 1
  • 3
  • 6
    If we knew what you needed it for we might be able to give you better answers. – daramarak Jan 04 '11 at 15:37
  • @MDC: Based on your comments, you should post a new question that includes the code you're currently using, an example of the syntax you *want* to use, and the needed behavior for the desired syntax. By focusing on the problem that needs to be solved, we can show you the technology to use. – John Dibling Jan 04 '11 at 15:52
  • Instead of passing some hashed key to map the `__FILE__` and `__LINE__` directives, why not just use the `__FILE__` and `__LINE__` directives as part of your exception class? – Zac Howland Jan 04 '11 at 15:58
  • That was my original plan, but a key is requested. It may be because of security, performance or simplicity reasons. – MDC Jan 04 '11 at 16:03
  • 1
    What does it exactly mean *a key is requested*? Who is *requesting*? why? I don't see how passing `__FILE__` and `__LINE__` may be *secure*, on *performance* looking up in a map/hash table will not be better than just receiving two constants in the call, (copy `const char*` and an `int` as compared to copy a single `int` in the case of failure), and *simplicity*, it's surely going to be much more complex than passing the actual values you need! – David Rodríguez - dribeas Jan 04 '11 at 16:56
  • Also, you might be focusing on a solution rather than the problem... if you move the macro outwards, the solution will become suddenly simple: `SET_RELEASE_CAUSE( ingress, CauseCode::CALL_REJECTED );` I don't think you can get simpler than that, and that is trivially implemented with a macro similar to the block at the end. – David Rodríguez - dribeas Jan 04 '11 at 17:00
  • We may end up doing it this way. I am trying to avoid it so the syntax and interface changes are as simple as possible, but it might not be that bad. It would require making some private variables public. – MDC Jan 04 '11 at 18:33
  • I found out 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. – MDC Jan 04 '11 at 19:28

4 Answers4

4

No, this syntax is not valid in C++. It doesn't make much sense, either.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • (An expression and a declaration are two separate things.) – Lightness Races in Orbit Jan 04 '11 at 15:35
  • Right now, the program uses a message class to send exception information. I'm writing a macro to wrap this class into a new class: WRAPPER_MACRO(old_class_object) will expand to NewClass(old_class_object, key_value). If I add the static variable declaration as a second line right before this, it should work. The problem is that in most places in the code, the old class object 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. – MDC Jan 04 '11 at 15:47
  • I don't understand the need for a function-static variable. – Lightness Races in Orbit Jan 04 '11 at 15:49
  • The intention in the code above would be to make h static to main, initialize it to 2, and send 2 to foo(). Seems sort of reasonable. :-) – MDC Jan 04 '11 at 18:43
  • It's the declaration part that really messes it up. We could put an assignment statement there, just can't have the static int declaration too. – MDC Jan 04 '11 at 18:45
  • I don't see why it's reasonable. Just send 2. – Lightness Races in Orbit Jan 04 '11 at 22:24
  • I give up. I still have no idea what on earth you're on about. Good luck! – Lightness Races in Orbit Jan 04 '11 at 22:32
  • I was thinking that if it is static it would be initalized once at program startup. Is this not the case? How can I get a variable that is initialized by the compiler only at program startup? – MDC Jan 04 '11 at 22:44
  • You can't just throw "static" in front of any variable, anywhere. You also can't put declarations just anywhere you please. Additionally, "static" has multiple, distinct meanings in C++. I hate to sound unhelpful but I really suggest reading a decent C++ book. :) – Lightness Races in Orbit Jan 04 '11 at 22:45
  • Well, the idea is getting a variable that gets initalized once at startup like a Java static initialization block. – MDC Jan 04 '11 at 22:47
  • A static variable would retain its value between function calls. We would just need it initialized to the key value and then let be. – MDC Jan 04 '11 at 22:50
  • A global variable will retain its value for the program's lifetime (and have what's known as "static storage duration"). Please go ahead and read that C++ book. – Lightness Races in Orbit Jan 04 '11 at 22:50
  • I was thinking of using a global variable... declare it globally and then we can remove the declaration and keep the assignment. Problem is that the calculation would still be done every time. We want the calculation done once per line. – MDC Jan 04 '11 at 22:51
  • A constant that can be defined by a function would do the trick, for example. – MDC Jan 04 '11 at 22:53
  • Spewing endless terminology without knowing what you're saying is not useful. – Lightness Races in Orbit Jan 04 '11 at 22:56
  • I am not discouraged, because every wrong attempt discarded is another step forward. – MDC Jan 04 '11 at 22:58
  • Your steps are sideways. Read a C++ book. – Lightness Races in Orbit Jan 04 '11 at 22:59
  • I'm not sure a C++ would show me how to do this particular thing. – MDC Jan 04 '11 at 23:00
  • A C++ book would teach you C++, which would inform you as to what tools are at your disposal in the language, and which tools simply do not exist. – Lightness Races in Orbit Jan 04 '11 at 23:02
  • No, you cannot learn C++ from the internet. Get a good book. Some recommended ones are listed here: http://jcatki.no-ip.org/fncpp/Resources. Good luck. – Lightness Races in Orbit Jan 04 '11 at 23:08
  • You cannot learn C++ from the internet? I find that hard to believe. – MDC Jan 04 '11 at 23:24
  • @MDC: I'm sorry to hear that. Nonetheless, it is true; you will learn only falsehoods from internet "tuts". Get a book. – Lightness Races in Orbit Jan 04 '11 at 23:30
  • I've got the basics, just need a reference to refresh some concepts. I found a solution above that uses constants. I haven't given up on the idea of using a static variable which will maintain its value. – MDC Jan 05 '11 at 01:35
  • You don't have the basics at all. – Lightness Races in Orbit Jan 05 '11 at 09:23
  • Trying to declare and define a static variable in a function argument list exhibits a lack of understanding of the basics of C++'s objects and functions. Sorry! – Lightness Races in Orbit Jan 05 '11 at 17:42
  • No, no, the question was really, is there any way to do this? As in, how would you get this result with other code. I'm sorry you didn't understand. – MDC Jan 06 '11 at 13:09
  • This question is no longer an issue. I got approval yesterday to place the hash function in the new class's constructor - which I wanted to do originally. – MDC Jan 06 '11 at 13:14
  • You can get the same result (no compilation) by entering random characters into a `.cpp` file and running it through `g++`. :) – Lightness Races in Orbit Jan 06 '11 at 14:20
4

Here's how I typically accomplish what you're trying to do in your larger problem. I'll start from the base & work my way up. You take take pieces from this that you find useful.

First, I'll start with a general exception library. You may or may not use exceptions, but the concepts may be similar.

class generic_error : virtual public std::exception
{
public:
    inline generic_error(const std::string& err);
    inline const char* what() const;

    std::string err_;
};

This is used by simply throwing these exceptions:

throw(generic_error("Something Bad Happened"));

This has no file & line number info attached to it, but I want that for tracing & logging. I have dozens of different exceptions in my library, so I sure don't want to change those. Instead, I could define a new exception type that encompasses just the file & line info:

class traced_error : virtual protected std::exception
{
public:
    traced_error(const std::string& file, int line);    
    const char* what() const;
    int line_;
    std::string file_;    
};

But now the original exception is lost. No problem. I can make traced_error derive from the original exception. By making traced_error a class template, I can parameterize the original exception (non-production-quality example code shown):

template<class EX>
class traced_error : virtual protected std::exception, virtual public EX
{
public:
    traced_error(const std::string& file, int line, const EX& ex)
    :   EX(ex), line_(line), file_(file) {};
    const char* what() const
    {
      std::stringstream ss;
      ss << "Exception '" << EX::what() << "' @ " << file_ << ":" << line_;
      return ss.str().c_str();
    }
    int line_;
    std::string file_;    
};

And we can make a little helper utility function that returns this exception by-value:

template<class EX> traced_error<EX> make_traced_error(const std::string& file, int line, const EX& ex)
{
    return traced_error<EX>(file, line, ex);
}

Now all we need is a macro that embeds FILE and LINE in a call to make_traced_error:

#define throwx(EX) (throw make_traced_error(__FILE__,__LINE__,EX))

With all this in place, we can port our code easily. Anywhere we threw the original exception like this:

throw(generic_error("Somethign Bad Happened"));

We can simply use throwx instead of throw:

throwx(generic_error("Somethign Bad Happened"));

EDIT:

The compiler will take the above code and resolve the macro. The resulting code will look like this:

throw(make_traced_error(__LINE__, __FILE__, generic_error("Something Bad Happened"));

The resulting exception object is of type traced_exception<generic_error>. Line & file info will be embedded in traced_error and can be extracted by calling traced_error::what(). "Something Bad Happened" will be embedded in the generic_error and can be extracted by calling generic_error::what()

Since traced_error<generic_error> is derived from generic_error, any code that catches a generic_error by reference doesn't need to change:

catch(const generic_error& ex)
{
  cout << ex.what();
}
R Yoda
  • 8,358
  • 2
  • 50
  • 87
John Dibling
  • 99,718
  • 31
  • 186
  • 324
  • @MDC: I tried to describe this in evolutionary steps, the way I developed it. At the top is my original code, and at the bottom is the new code. – John Dibling Jan 04 '11 at 17:03
  • @MDC: Added a bit more that might help fill in some corners. – John Dibling Jan 04 '11 at 17:11
  • This makes sense. There is a tracing mechanism in the program that uses a macro to do this. The problem is creating the key values for each line. If we can't generate the key values at compile time/start up, we may end up either just 1. sending the filename and line number or 2. having the constructor calculate the key value each time. – MDC Jan 04 '11 at 18:06
  • The program has an existing tracing mechanism that uses the _FILE_ and _LINE_ macro which writes to log files. The new feature is to store the key values into a separate file that contains per call info. – MDC Jan 04 '11 at 18:10
  • @John, brilliant! I couldn't think of a more clear solution. This deserves upvotes. – Kos Jan 20 '11 at 13:47
  • 1
    but does it totally need multiple / virtual inheritance? Couldn't traced_error only inherit from Ex, assuming it will be a subclass of std::exception already? And why generic_error inherits from std::exception virtually? – Kos Jan 20 '11 at 13:51
  • 1
    @Kos: Good question(s). `generic_error` derives from `std::exception` virtually because it "knows" that the concrete type might use multiple inheritance. I remember when I was researching the design for this I wanted `traced_error` to be it's own thing, hence I needed MI. I can't remember the reasons for this decision right now as I've only had one cup of coffee. It's possible this could be reengineered the way you suggest. If so, that would likely be better. – John Dibling Jan 20 '11 at 13:56
  • `what()` causes an compiler error in my case with C++14: "error: exception specification of overriding function is more lax than base version". Solution: Add `noexcept` at the end of the function signature. Or better: Inherit from `std::runtime_exception` where you can already pass a string to its constructor! – R Yoda Nov 29 '18 at 21:58
  • **Note:** `ss.str().c_str()` will cause **a dangling pointer** if the underlying object is destructed (see stackoverflow.com/q/34411874/4468078) . Since the string's memory is not stored in the `traced_error` class the lifetime is bound to the function scope (causing a dangling pointer IMHO). – R Yoda Nov 29 '18 at 23:56
  • The reason for virtual inheritance for exceptions is explained here: https://stackoverflow.com/q/5874255/4468078 – R Yoda Dec 01 '18 at 00:45
1

I have no idea what that would even mean. How can a value passed on the stack possibly be static?

Perhaps you can flesh out your explanation a little more, but seems to me the answer is a definite No.

Jonathan Wood
  • 65,341
  • 71
  • 269
  • 466
  • The problem I'm trying to solve is getting the filename and line number with the __FILE__ and __LINE__ macros provided by the preprocessor, but 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 there. Right now, the continued.. – MDC Jan 04 '11 at 15:43
  • How a value is passed and where it is defined are completely separate things. I did not understand the question either, but this answer is incorrect. *How can a value passed on the stack possibly be static?*: `void foo() { static int i = 0; bar(i); } void bar( int x ) {}`. The value will be passed according to the calling convention (which in some cases will be the stack) and at the same time the variable is `static`... – David Rodríguez - dribeas Jan 04 '11 at 15:46
  • @MDC: why do you need the variable? will you use it later in the same function? other functions? --at any rate, add the comment above and the answer to these questions to the original question rather than as comments to an answer. – David Rodríguez - dribeas Jan 04 '11 at 15:48
  • The variable's value will be sent to the message object as the key identifying the filename and line number. It is static so that it is only calculated once per line. – MDC Jan 04 '11 at 15:52
  • Well, it looks like the preprocessor will still run before the functions get inlined. – MDC Jan 04 '11 at 21:59
1

No there is no way to expand a function argument expression into a variable declaration.

One way to work around this though is to use a macro to control the calls to the function and have that expand to include the variable. For example

#define foo { static int h = 2; _foo(h); }
void _foo(int b) {
  ...
}

This trick is limited though because it only works for void returning functions.

Can you elaborate a bit on the larger scenario here? There is very likely a much better solution looming.

JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454