sharkdp / dbg-macro

A dbg(…) macro for C++
MIT License
2.96k stars 254 forks source link

Is it possible to allow variadic template expansion? #109

Open anstadnik opened 3 years ago

anstadnik commented 3 years ago

I'd like to be able to write

    dbg(args...);

So far it breaks when the macro is expanded. The workaround might be to create a tuple from a variadic template like this:

    std::tuple<ARGS...> input{args...};
    dbg(input);

But it adds some unwanted output regarding the input type (std::tuple<...), and has other issues (see #102)

sharkdp commented 3 years ago

Thank you for reporting this. It would be great if we could support this.

Can you provide a small example to reproduce the error?

anstadnik commented 3 years ago

Yes, sure

template <typename... ARGS>
void test(const ARGS&... args) {
  dbg("Args: ", args...);
}

int main(void)
{
  test(1);
  test('1');
  return 0;
}
../src/main.cpp:5:21: error: expected ')'
  dbg("Args: ", args...);
                    ^
../src/main.cpp:5:3: note: to match this '('
  dbg("Args: ", args...);
  ^
.../dbg.h:808:23: note: expanded from macro 'dbg'
             {DBG_MAP(DBG_TYPE_NAME, __VA_ARGS__)}, __VA_ARGS__)
                      ^
../src/main.cpp:5:3: error: expression contains unexpanded parameter pack 'args'
  dbg("Args: ", args...);
  ^             ~~~~
.../dbg.h:806:3: note: expanded from macro 'dbg'
  dbg::DebugOutput(__FILE__, __LINE__, __func__)    \

The problem seems to occur because the macro expands earlier than the variadic template.

sharkdp commented 3 years ago

Thank you. I can reproduce it as well.

Unfortunately, I don't really know how to fix it. Any help would be very much appreciated.

anstadnik commented 3 years ago

Seems the problem in that even the following code doesn't compile:

#include <typeinfo>

template <typename... T>
void test(T&& ...args) {
  /* std::cout << typeid(args...).name() << std::endl; */
  typeid(args...).name();
  // typeid(1).name();
};

int main(void)
{
  test('v', 1);
  return 0;
}

I'm afraid it cannot be called that way. You can move the type extraction inside the print_impl, but it will output the wrong info when the variable was not a reference, for example.

sharkdp commented 3 years ago

Maybe @ShikChen could help out here? (only if you are interested)

ShikChen commented 3 years ago

I don't have a good solution in mind now. Note that it's also really tricky to properly handle the return value:

#include <dbg.h>

template <typename... T>
int sum(T&&... args) {
  return (args + ...);
}

template <typename... T>
int calc(T&&... args) {
  // Some other logic here in real world...

  // This will return 3 instead of 6 :O
  // return sum(dbg::identity(args...));

  return sum(args...);
}

int main() {
  dbg(calc(1, 2, 3));
  return 0;
}

A slightly simpler workaround could be wrapping it with tie() like dbg(std::tie(args...)).

anstadnik commented 2 years ago

I've recently found a hack which might allow this. Consider the following example:

template <int N, typename... Ts>
using NthTypeOf = typename std::tuple_element<N, std::tuple<Ts...>>::type;

template <typename... ARGS>
void f(ARGS&&... args) {
  dbg::DebugOutput(__FILE__, __LINE__, __func__)
      .print({DBG_MAP(DBG_STRINGIFY, args...)},
             {dbg::type_name<NthTypeOf<0, ARGS...>>() + ", " +
              dbg::type_name<NthTypeOf<1, ARGS...>>()},
             args...);  // Here should be logic, which would evaluate args as a
                        // single variable
}

template <typename... ARGS>
void proxy(ARGS&&... args) {
  dbg::DebugOutput(__FILE__, __LINE__, __func__)
      .print({DBG_MAP(DBG_STRINGIFY, args...)},
             {dbg::type_name<NthTypeOf<0, ARGS...>>() + ", " +
              dbg::type_name<NthTypeOf<1, ARGS...>>()},
             args...);  // Here should be logic, which would evaluate args as a
                        // single variable
  f(args...);
}

int main(void) {
  proxy(1, 'v');
  // test('1');
  return 0;
}

It outputs

[..h_dbg_2/src/main.cpp:20 (proxy)] The number of arguments mismatch, please check unprotected comma
[..h_dbg_2/src/main.cpp:20 (proxy)] args... = 1 (int, char)
[..h_dbg_2/src/main.cpp:20 (proxy)]  = 'v' (W5[U-Usrc/main.cpp:20 (proxy)] args... = 1 (int, char)
)
[..h_dbg_2/src/main.cpp:10 (f)] The number of arguments mismatch, please check unprotected comma
[..h_dbg_2/src/main.cpp:10 (f)] args... = 1 (int&, char&)
[..h_dbg_2/src/main.cpp:10 (f)]  = 'v' (W5[U-Usrc/main.cpp:10 (f)] args... = 1 (int&, char&)
)

Note that it compiles, and manages to correctly detect when parameter pack variables are lvalue reference. Though it seems to fail when they're rvalue references. I had to manually expand the macro, to replace the logic for getting the variables' types. Do you think the logic of dbg can be rewritten to use this?

anstadnik commented 2 years ago

Hm, also what's interesting that this seems to work!

template <typename... ARGS>
void proxy(ARGS&&... args) {
  /* dbg::DebugOutput(__FILE__, __LINE__, __func__)
    .print({DBG_MAP(DBG_STRINGIFY, args...)},
           {(... + (" " + dbg::type_name<decltype(args)>()))},
           args...); */
  std::cout << (... + (" " + dbg::type_name<decltype(args)>())) << std::endl;
}

int main(void) {
  proxy(1, 'v');
  // test('1');
  return 0;
}

I think this might fix the issue with finding the appropriate types for the parameter pack.

Though it's still unclear how to threat parameter pack in a different way. So basically for usual variables we wanna do

#define DBG_TYPE_NAME(x) dbg::type_name<decltype(x)>()

and for parameter pack:

#define DBG_TYPE_NAME(x) (... + (" " + dbg::type_name<decltype(args)>()))

I guess it might be solvable somehow using templates or recursion, but I can't find the solution right now