-2

How can I write a macro that does different things based on the type of its arguments?

I have a macro that needs to handle an argument that can have one of two types.

#include <typeinfo>

enum class Coolness { kUndefined, kUncool, kCool };
enum class Tallness { kUndefined, kShort, kTall };

void MakePerson (Coolness coolness, Tallness tallness) {}

// Provide a way to make a person by only providing Coolness or Tallness.
#define MAKE_PERSON(x)                         \
({                                             \
  if (typeid(x) == typeid(Coolness)) {         \
      MakePerson(((x)), Tallness::kUndefined); \
  } else {                                     \
      MakePerson(Coolness::kUndefined, (x));   \
  }                                            \
})

int main()
{
  MAKE_PERSON(Coolness::kUncool);
  MAKE_PERSON(Tallness::kTall);
}

(We could use default arguments here, but in the real code we effectively must use a macro.)

The compiler throws an error on both calls in main:

main.cpp: In function ‘int main()’:
main.cpp:23:43: error: cannot convert ‘Coolness’ to ‘Tallness’ for argument ‘2’ to ‘void MakePerson(Coolness, Tallness)’
       MakePerson(Coolness::kUndefined, (x)); \
                                           ^
main.cpp:29:3: note: in expansion of macro ‘MAKE_PERSON’
   MAKE_PERSON(Coolness::kUncool);
   ^~~~~~~~~~~
main.cpp:21:45: error: cannot convert ‘Tallness’ to ‘Coolness’ for argument ‘1’ to ‘void MakePerson(Coolness, Tallness)’
       MakePerson(((x)), Tallness::kUndefined); \
                                             ^
main.cpp:30:3: note: in expansion of macro ‘MAKE_PERSON’
   MAKE_PERSON(Tallness::kTall);
   ^~~~~~~~~~~

(done on https://www.onlinegdb.com/online_c++_compiler)

We can't use __builtin_types_compatible_p as in this question because our compiler doesn't have that.

How can I write a macro that does different things based on the type of its arguments?

A.Wan
  • 1,818
  • 3
  • 21
  • 34
  • 5
    This is what C++ templates are for. This is C++, not prehistoric C with *shudder* macros... – Sam Varshavchik Jun 15 '19 at 01:48
  • Unfortunately I think I need to use macros in the real-world problem - I'm working on logging infrastructure that needs access to `__LINE__` and `__FILE__`. This was just the smallest example I could come up with. – A.Wan Jun 15 '19 at 02:27
  • 3
    Since `__LINE__ ` and `__FILE__` are always the same type, everywhere, it is unclear what that has to do with anything. – Sam Varshavchik Jun 15 '19 at 02:45
  • @SamVarshavchik, what I was trying to say was that I'm working on enabling LOG(...) calls, and I think I need to use a macro here so that I can get the line# and file of where the call happens (otherwise I'll just get the line# and file of where the log cal is implemented). But I suppose you were saying that the implementation of the macro should use C++ templates, not that macros should not be used, ever? – A.Wan Jun 17 '19 at 18:22

2 Answers2

5

Use simple function overloading, don't try to make the macro smarter than it needs to be:

enum class Coolness { kUndefined, kUncool, kCool };
enum class Tallness { kUndefined, kShort, kTall };

void MakePerson (Coolness coolness, Tallness tallness)
{
    ...
}

inline void MakePerson (Coolness coolness)
{
    MakePerson(coolness, Tallness::kUndefined);
}

inline void MakePerson (Tallness tallness)
{
    MakePerson(Coolness::kUndefined, tallness);
}

#define MAKE_PERSON(x) \
{ \
    // use __FILE__ and __LINE__ as needed... \
    MakePerson(x); \
}

int main()
{
    MAKE_PERSON(Coolness::kUncool);
    MAKE_PERSON(Tallness::kTall);
}

Live Demo

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thanks, this is clearly the way to solve the example I gave. I think it illustrates how I need to rework my actual code so we can get closer to this implementation... – A.Wan Jun 17 '19 at 18:26
-2

Other suggestions are welcome, but what we eventually did was use a static_cast to tell the compiler what type the arguments are:

#include <typeinfo>

enum class Coolness { kUndefined, kUncool, kCool };
enum class Tallness { kUndefined, kShort, kTall };

void MakePerson (Coolness coolness, Tallness tallness) {}

// Provide a way to make a person by only providing Coolness or Tallness.
// Static cast is used because the compiler fails to typecheck the 
// branches correctly without it.
#define MAKE_PERSON(x)                                              \
({                                                                  \
  if (typeid(x) == typeid(Coolness)) {                              \
      MakePerson(static_cast<Coolness>((x)), Tallness::kUndefined); \
  } else {                                                          \
      MakePerson(Coolness::kUndefined, static_cast<Tallness>((x))); \
  }                                                                 \
})

int main()
{
  MAKE_PERSON(Coolness::kUncool);
  MAKE_PERSON(Tallness::kTall);
}
...Program finished with exit code 0 
A.Wan
  • 1,818
  • 3
  • 21
  • 34
  • 4
    This is horrible. Also it is not clear why you do `static_cast` when you already just checked the type was `Coolness`. – M.M Jun 15 '19 at 02:56
  • If your issue is with the wrong branch of the `if` not typechecking, consider `constexpr if`, rather than sloppily hiding the type error with an invalid cast. – Silvio Mayolo Jun 15 '19 at 05:50
  • @SilvioMayolo thanks, I didn't know about `constexpr if`. Unfortunately I can't use that since I'm limited to C++11. – A.Wan Jun 17 '19 at 18:13
  • @M.M added a comment to explain why I did the static_cast, thanks (not saying that this makes it better but hopefully it helps explain why). – A.Wan Jun 17 '19 at 18:15