Passing array initialisation as argument in macro

I have a macro that logs stuff. I want to be able to pass array initialisation which was achieved simply by using variadic macros.
Now I've expanded my macro to allow for up to 5 arguments to be passed in and it works great EXCEPT things like array initialisation which errors because of the extra commas.
I can wrap the array in brackets and it works fine but this means the user has to remember to do this and I don't want that. I cannot seem to find a way to allow lists like that to be passed in or to wrap the arguments with () and prevent the error.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#define func(args...) func_x(, ##args, func_3(args), func_2(args), func_1(args), func_0())
#define func_x(x, A, B, C, Func, ...) Func 
#define func_VA(args...) std::cout << #args << std::endl
#define func_0() std::cout << "0" << std::endl;
#define func_1(A1) func_VA(A1)
#define func_2(A1, A2) func_1(A1), func_VA(A2)
#define func_3(A1, A2, A3) func_2(A1, A2), func_VA(A3)

func();
func(1);
func(1, 2);
func(1, 2, 3);
// Does not work unless func((std::vector<int>{0,1,2,3,4}));
func(std::vector<int>{0,1,2,3,4});


This is not what it actually does, yes it needs to be macros.
Last edited on
> yes it needs to be macros.
And why can't it be variadic templates?
I'm fairly sure it can't work with templates like that. It needs to work like this :

1
2
3
4
5
void test_func(int i, bool b, std::string str) {
  std::cout << "We got " << i << " and " << b << " and " << str << std::endl;
}
//
test_func(func(4, true, "hello"));


Which set up like this will. Obviously subbing the cout for what it actually does.
It actually works like so :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
class DebugOutput {
 public:
  DebugOutput(const char* filepath,
              int line,
              const char* function_name,
              const char* expression)
      : m_use_colorized_output(isColorizedOutputEnabled()),
        m_filepath(filepath),
        m_line(line),
        m_function_name(function_name),
        m_expression(expression) {
    const std::size_t path_length = m_filepath.length();
    if (path_length > MAX_PATH_LENGTH) {
      m_filepath = ".." + m_filepath.substr(path_length - MAX_PATH_LENGTH,
                                            MAX_PATH_LENGTH);
    }
  }

  template <typename T>
  T&& print(const std::string& type, T&& value) const {
    const T& ref = value;
    std::stringstream stream_value;
    const bool print_expr_and_type = pretty_print(stream_value, ref);

    std::stringstream output;
    output << ansi(ANSI_DEBUG) << "[" << m_filepath << ":" << m_line << " ("
           << m_function_name << ")] " << ansi(ANSI_RESET);
    if (print_expr_and_type) {
      output << ansi(ANSI_EXPRESSION) << m_expression << ansi(ANSI_RESET)
             << " = ";
    }
    output << ansi(ANSI_VALUE) << stream_value.str() << ansi(ANSI_RESET);
    if (print_expr_and_type) {
      output << " (" << ansi(ANSI_TYPE) << type << ansi(ANSI_RESET) << ")";
    }
    output << std::endl;
    std::cerr << output.str();

    return std::forward<T>(value);
  }

 private:
  const char* ansi(const char* code) const {
    if (m_use_colorized_output) {
      return code;
    } else {
      return ANSI_EMPTY;
    }
  }

  const bool m_use_colorized_output;

  std::string m_filepath;
  const int m_line;
  const std::string m_function_name;
  const std::string m_expression;

  static constexpr std::size_t MAX_PATH_LENGTH = 20;

  static constexpr const char* const ANSI_EMPTY = "";
  static constexpr const char* const ANSI_DEBUG = "\x1b[02m";
  static constexpr const char* const ANSI_EXPRESSION = "\x1b[36m";
  static constexpr const char* const ANSI_VALUE = "\x1b[01m";
  static constexpr const char* const ANSI_TYPE = "\x1b[32m";
  static constexpr const char* const ANSI_RESET = "\x1b[0m";
};
//
#define func_VA(...)                                                  \
  dbg_macro::DebugOutput(__FILE__, __LINE__, __func__, #__VA_ARGS__) \
      .print(dbg_macro::type_name<decltype(__VA_ARGS__)>(), (__VA_ARGS__)) 

I can't see how to make this work with initialisation lists, multiple arguments, and passing values.
I'm fairly sure it can't work with templates like that.


Is this what you want?
1
2
3
4
5
template <typename... Args>
std::ostream& print(std::ostream& os, Args&&... args) 
{ return (os << ... << std::forward<Args>(args)); }

print(std::cerr, "we got ", 2, " and ", 4.2, " and ", 100, '\n');

Live demo:
http://coliru.stacked-crooked.com/a/234137ffdda6292b
Last edited on
No that does not to seem to work the way I need it to.
It needs to be able to return multiple types so it needs to be split into multiple calls.

As shown in my last comment it can be used inside expressions. I don't have too too much knowledge on template stuff with c++ so perhaps we can continue working on this together.

I still think if there is some way we can wrap the vectors or similar in () would be great. Or simply to allow for these initialisation in macro.
func( ( std::vector<int>{0,1,2,3,4} ) );
> I can wrap the array in brackets and it works fine but I don't want that.
> // Does not work unless func((std::vector<int>{0,1,2,3,4}));

There is no work-around; the preprocessor does not understand C++ syntax.

if an argument of a function-like macro includes commas that are not protected by matched pairs of left and right parentheses, the comma is interpreted as macro argument separator, causing a compilation failure due to argument count mismatch. https://en.cppreference.com/w/cpp/preprocessor/replace
Why do you want to do this:
fn(this_debug_macro_expands_to_an_arglist(x, y), z)
When you can simply write a second line of code to log x and y.

Note that this syntax is achievable if we hook fn and change the macro to a function template:

1
2
3
4
inline constexpr auto fn = bind_front(debug_invoke, fn_impl);
// ... 
fn(debug(x, y), z); // or
debug_invoke(fn_impl, debug(x, y), z);

Last edited on
yes it needs to be macros.
I assume that's because you need to remove the debug code for production release.

The solutions so far are all ways to create a macro that dumps the data to a stream. Have you thought of this approach:

1
2
3
#define DEBUG(x) (x)
...
DEBUG(cerr << "here is my debug statement << a << b << c << '\n'); 

For release, you simply change the macro to
#define DEBUG(x)

dhayden, yes that would work for that purpose.
This however has custom logging and can pass values through. My problem is purely with getting initialisation lists to print.
I understand why the commas break it but am still wondering if there is something we can do about it.
This however has custom logging and can pass values through
Please elaborate. What do you mean by "custom logging?" What do you mean by "pass values through?" The example I gave can pass values (and anything else) through.

One other suggestion which we've used at word: create a custom streambuf that writes your logging data. Then you can attach an ostream to it and write whatever you like.
Topic archived. No new replies allowed.