0

I want to write a simple Macro function. Because this macro is used in many places by different normal c++ functions, I encountered a variable scope issue. I would like to know if there is a quick way to solve it? Thank you very much.

As you can see in the attached code, depending on whether the macro is called in the function for the first time or not, I want to either declare or reuse the variable ptrCandidate. Note the variable scope is in the function, not in the file or translation unit. In other words, every time the macro is invoked in a new function for the 1st time, I want the top macro. And within the same function, if the macro is invoked again, I want the bottom macro.

#define EXPECT_MY_CLASS_EQ(expectedStr, candidateStr)       \
    auto ptrCandidate = parseAndGetPtr(candidateStr);       \
    doWork(ptrCandidate);                                   \
    EXPECT_EQ(expectedStr, convertToString(ptrCandidate));

#define EXPECT_MY_CLASS_EQ(expectedStr, candidateStr)       \
    ptrCandidate = parseAndGetPtr(candidateStr);            \
    doWork(ptrCandidate);                                   \
    EXPECT_EQ(expectedStr, convertToString(ptrCandidate));
void foo(){
    EXPECT_MY_CLASS_EQ("123","abcd")
}
void bar(){
    EXPECT_MY_CLASS_EQ("111","aabb")
    EXPECT_MY_CLASS_EQ("222","ccdd")
}
void foo(){
    auto ptrCandidate = parseAndGetPtr("abcd");
    doWork(ptrCandidate);
    EXPECT_EQ("123", convertToString(ptrCandidate));
}
void bar(){
    auto ptrCandidate = parseAndGetPtr("aabb");
    doWork(ptrCandidate);
    EXPECT_EQ("111", convertToString(ptrCandidate));

    /* auto */ ptrCandidate = parseAndGetPtr("ccdd");
    doWork(ptrCandidate);
    EXPECT_EQ("222", convertToString(ptrCandidate));
}
double-beep
  • 5,031
  • 17
  • 33
  • 41
BlowMyMind
  • 435
  • 4
  • 6
  • 2
    You could use the [`do ... while (false);`](https://stackoverflow.com/questions/4674480/do-whilefalse-pattern) pattern to put the macro's content inside a new scope. – alter_igel Apr 17 '19 at 22:16
  • 1
    Is it important to re-use the same variable? Otherwise I would just introduce a new scope in the macro – Cybran Apr 17 '19 at 22:20

3 Answers3

4

As shown in another answer, you don't need a macro in this case.

Generally speaking though, you can avoid re-definitions of variable names by the following means:

Note that each of these approaches actually creates a new variable each time, so is semantically different from your approach with two separate macros! This is especially important if the type of the (assigned) variable has a non-default assignment operator!

If, for some reason, you rely on the reuse of a single variable and the assignment to it, then IMO the easiest approach is to define two macros. One macro which declares the variable (and initializes it, if necessary), and another macro with the code which uses the variable.

Daniel Jour
  • 15,896
  • 2
  • 36
  • 63
  • What if, in some complicated huge program with 40 different directories nested at different levels, I use that pasty-indirection-unique-variable macro on line 10 of file blah.cpp, in directory feature1/stuff/foo/, and *just happen* to also use the same macro in baz.cpp in driver/core/bar/, and *by coincidence* it happens to be on line 10 of that file. What exactly in this hand wavy incomplete recommendation prevents baz.cpp's "unique symbol" from colliding with blah.cpp's "unique symbol"? (It isn't `__LINE__`, that's for sure!) – H Walters Apr 18 '19 at 02:51
  • ...o/c, this comment is specifically about (and limited to) the recommendation of the use of `__LINE__` and `__COUNTER__` to generate "unique" variables (and this is easily mitigated e.g., by putting them an unnamed ns, but I would think that should have to be mentioned). – H Walters Apr 18 '19 at 02:57
  • @HWalters If the probability of that happening is more than minimal to impossible then you have way too many global variables. And all you'll get is an compiler error (redefinition). – Daniel Jour Apr 18 '19 at 11:54
  • Unnamed namespaces are IMO on a different level .. this post is about unique variable names within a translation unit. This is not about linking. – Daniel Jour Apr 18 '19 at 11:55
  • "this post is about" *This* question is about *scoped* variables, but I see nothing mentioning a single t.u. But your *bullet* is listed under "generally speaking", which I read as importing scope (it's superfluous for *this* question *anyway*). "probability" ...it's akin to [the birthday problem](https://en.wikipedia.org/wiki/Birthday_problem) (two t.u.'s, same line); we're not talking won-the-lottery. "too many globals"/"all you'll get" ...I'm reading, "if my advice bites you, you did something wrong, and my advice is still perfectly fine because if it bites you it won't hurt much". – H Walters Apr 18 '19 at 14:31
  • @HWalters The questions contains code which is only valid *within* a function. Thus global variables aren't really what this question is about. Thus I didn't include approaches for resolving the possible *linking* issues which could occur using it. Note that the *linking* issue which you mentioned **requires** two separate translation units ... unless you merge feature1/stuff/foo/blah.cpp and driver/core/bar/baz.cpp into a single file or do some other trickery (including source files instead of headers for example). – Daniel Jour Apr 18 '19 at 21:05
  • @HWalters Long story short: What you mention *can* be a problem with this approach *when* applied to global variables *and* when not following proper practices (putting "local" definitions in an unnamed namespace). It's completely orthogonal to the questions though. Last but not least I don't "recommend" any approach here, I just list the possibilities. Which approach is suitable or not depends on the circumstances (which are - again - not the scope of the question). – Daniel Jour Apr 18 '19 at 21:08
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/192080/discussion-between-h-walters-and-daniel-jour). – H Walters Apr 19 '19 at 01:46
2

It seems regular function works:

void EXPECT_MY_CLASS_EQ(const char* expectedStr, const char* candidateStr)
{
    auto ptrCandidate = parseAndGetPtr(candidateStr);
    doWork(ptrCandidate);
    EXPECT_EQ(expectedStr, convertToString(ptrCandidate));
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302
0

A possible way might be to use __LINE__ or __COUNTER__ with preprocessor symbol concatenation.

In your case, you probably don't need any macro: prefer some static inline function.

Here is a real-life example (using concatenation and __LINE__) from my Bismon's project file cmacros.h line 285 (it is in C, but the same trick could be done in C++)

#define LOCAL_FAILURE_HANDLE_ATBIS_BM(Fil,Lin,Lockset,Flabel,FcodVar,ReasonVar,PlaceVar) \
  struct failurehandler_stBM fh_##Lin                   \
   = {                                  \
     .pA = {.htyp = typayl_FailureHandler_BM},              \
     .failh_magic = FAILUREHANDLEMAGIC_BM,              \
     .failh_lockset = Lockset,                      \
     .failh_reason = NULL,                      \
     .failh_jmpbuf = {}};                       \
  curfailurehandle_BM = &fh_##Lin;                  \
  volatile int failcod_##Lin =  setjmp(fh_##Lin.failh_jmpbuf);      \
  FcodVar = failcod_##Lin;                      \
  if (failcod_##Lin) {                          \
    ReasonVar = fh_##Lin.failh_reason;                  \
    PlaceVar = fh_##Lin.failh_place;                    \
    goto Flabel;                            \
  };                                    \
  (void)0

#define LOCAL_FAILURE_HANDLE_AT_BM(Fil,Lin,Lockset,Flabel,FcodVar,ReasonVar,PlaceVar) \
  LOCAL_FAILURE_HANDLE_ATBIS_BM(Fil,Lin,Lockset,Flabel,FcodVar,ReasonVar,PlaceVar)

/// code using LOCAL_FAILURE_HANDLE_BM should probably backup and
/// restore the curfailurehandle_BM
#define LOCAL_FAILURE_HANDLE_BM(Lockset,Flabel,FcodVar,ReasonVar,PlaceVar)  \
LOCAL_FAILURE_HANDLE_AT_BM(__FILE__,__LINE__,Lockset,Flabel,FcodVar,ReasonVar,PlaceVar)

Back to your question, if you still want a macro: just create a block, e.g.

#define EXPECT_MY_CLASS_EQ(expectedStr, candidateStr)   do{    \
    auto ptrCandidate = parseAndGetPtr(candidateStr);       \
    doWork(ptrCandidate);                                   \
    EXPECT_EQ(expectedStr, convertToString(ptrCandidate));} while(0)
Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547