Skip to content

Commit dce0091

Browse files
ShikChensharkdp
authored andcommitted
Support multiple arguments.
Fixes #2.
1 parent 4f7a7a5 commit dce0091

File tree

4 files changed

+137
-28
lines changed

4 files changed

+137
-28
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,17 @@ vcpkg install dbg-macro
9191

9292
## Advanced features
9393

94+
### Multiple arguments
95+
96+
Passing multiple arguments to the `dbg()` macro would just work. The output of
97+
`dbg(x, y)` is same as `dbg(x); dbg(y)`. If there are unprotected commas,
98+
please wrap them with parenthesis.
99+
100+
```c++
101+
dbg(42, "hello world");
102+
dbg(1, (std::vector<int>{2, 3, 4}), 5);
103+
```
104+
94105
### Hexadecimal, octal and binary format
95106
96107
If you want to format integers in hexadecimal, octal or binary representation, you can

dbg.h

Lines changed: 92 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -622,48 +622,79 @@ inline bool pretty_print(std::ostream& stream,
622622

623623
#endif
624624

625+
template <typename T, typename... U>
626+
struct last {
627+
using type = typename last<U...>::type;
628+
};
629+
630+
template <typename T>
631+
struct last<T> {
632+
using type = T;
633+
};
634+
635+
template <typename... T>
636+
using last_t = typename last<T...>::type;
637+
625638
class DebugOutput {
626639
public:
627-
DebugOutput(const char* filepath,
628-
int line,
629-
const char* function_name,
630-
const char* expression)
631-
: m_use_colorized_output(isColorizedOutputEnabled()),
632-
m_filepath(filepath),
633-
m_line(line),
634-
m_function_name(function_name),
635-
m_expression(expression) {
636-
const std::size_t path_length = m_filepath.length();
640+
// Helper alias to avoid obscure type `const char* const*` in signature.
641+
using expr_t = const char*;
642+
643+
DebugOutput(const char* filepath, int line, const char* function_name)
644+
: m_use_colorized_output(isColorizedOutputEnabled()) {
645+
std::string path = filepath;
646+
const std::size_t path_length = path.length();
637647
if (path_length > MAX_PATH_LENGTH) {
638-
m_filepath = ".." + m_filepath.substr(path_length - MAX_PATH_LENGTH,
639-
MAX_PATH_LENGTH);
648+
path = ".." + path.substr(path_length - MAX_PATH_LENGTH, MAX_PATH_LENGTH);
649+
}
650+
std::stringstream ss;
651+
ss << ansi(ANSI_DEBUG) << "[" << path << ":" << line << " ("
652+
<< function_name << ")] " << ansi(ANSI_RESET);
653+
m_location = ss.str();
654+
}
655+
656+
template <typename... T>
657+
auto print(std::initializer_list<expr_t> exprs, T&&... values)
658+
-> last_t<T...> {
659+
if (exprs.size() != sizeof...(values)) {
660+
std::cerr
661+
<< m_location << ansi(ANSI_WARN)
662+
<< "The number of arguments mismatch, please check unprotected comma"
663+
<< ansi(ANSI_RESET) << std::endl;
640664
}
665+
return print_impl(exprs.begin(), std::forward<T>(values)...);
641666
}
642667

668+
private:
643669
template <typename T>
644-
T&& print(const std::string& type, T&& value) const {
670+
T&& print_impl(const expr_t* expr, T&& value) {
645671
const T& ref = value;
646672
std::stringstream stream_value;
647673
const bool print_expr_and_type = pretty_print(stream_value, ref);
648674

649675
std::stringstream output;
650-
output << ansi(ANSI_DEBUG) << "[" << m_filepath << ":" << m_line << " ("
651-
<< m_function_name << ")] " << ansi(ANSI_RESET);
676+
output << m_location;
652677
if (print_expr_and_type) {
653-
output << ansi(ANSI_EXPRESSION) << m_expression << ansi(ANSI_RESET)
654-
<< " = ";
678+
output << ansi(ANSI_EXPRESSION) << *expr << ansi(ANSI_RESET) << " = ";
655679
}
656680
output << ansi(ANSI_VALUE) << stream_value.str() << ansi(ANSI_RESET);
657681
if (print_expr_and_type) {
658-
output << " (" << ansi(ANSI_TYPE) << type << ansi(ANSI_RESET) << ")";
682+
output << " (" << ansi(ANSI_TYPE) << type_name<T>() << ansi(ANSI_RESET)
683+
<< ")";
659684
}
660685
output << std::endl;
661686
std::cerr << output.str();
662687

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

666-
private:
691+
template <typename T, typename... U>
692+
auto print_impl(const expr_t* exprs, T&& value, U&&... rest)
693+
-> last_t<T, U...> {
694+
print_impl(exprs, std::forward<T>(value));
695+
return print_impl(exprs + 1, std::forward<U>(rest)...);
696+
}
697+
667698
const char* ansi(const char* code) const {
668699
if (m_use_colorized_output) {
669700
return code;
@@ -674,15 +705,13 @@ class DebugOutput {
674705

675706
const bool m_use_colorized_output;
676707

677-
std::string m_filepath;
678-
const int m_line;
679-
const std::string m_function_name;
680-
const std::string m_expression;
708+
std::string m_location;
681709

682710
static constexpr std::size_t MAX_PATH_LENGTH = 20;
683711

684712
static constexpr const char* const ANSI_EMPTY = "";
685713
static constexpr const char* const ANSI_DEBUG = "\x1b[02m";
714+
static constexpr const char* const ANSI_WARN = "\x1b[33m";
686715
static constexpr const char* const ANSI_EXPRESSION = "\x1b[36m";
687716
static constexpr const char* const ANSI_VALUE = "\x1b[01m";
688717
static constexpr const char* const ANSI_TYPE = "\x1b[32m";
@@ -696,14 +725,49 @@ T&& identity(T&& t) {
696725
return std::forward<T>(t);
697726
}
698727

728+
template <typename T, typename... U>
729+
auto identity(T&&, U&&... u) -> last_t<U...> {
730+
return identity(std::forward<U>(u)...);
731+
}
732+
699733
} // namespace dbg
700734

701735
#ifndef DBG_MACRO_DISABLE
702-
// We use a variadic macro to support commas inside expressions (e.g.
703-
// initializer lists):
704-
#define dbg(...) \
705-
dbg::DebugOutput(__FILE__, __LINE__, __func__, #__VA_ARGS__) \
706-
.print(dbg::type_name<decltype(__VA_ARGS__)>(), (__VA_ARGS__))
736+
737+
#define DBG_FOREACH_1(fn, x) fn(x)
738+
#define DBG_FOREACH_2(fn, x, ...) fn(x), DBG_FOREACH_1(fn, __VA_ARGS__)
739+
#define DBG_FOREACH_3(fn, x, ...) fn(x), DBG_FOREACH_2(fn, __VA_ARGS__)
740+
#define DBG_FOREACH_4(fn, x, ...) fn(x), DBG_FOREACH_3(fn, __VA_ARGS__)
741+
#define DBG_FOREACH_5(fn, x, ...) fn(x), DBG_FOREACH_4(fn, __VA_ARGS__)
742+
#define DBG_FOREACH_6(fn, x, ...) fn(x), DBG_FOREACH_5(fn, __VA_ARGS__)
743+
#define DBG_FOREACH_7(fn, x, ...) fn(x), DBG_FOREACH_6(fn, __VA_ARGS__)
744+
#define DBG_FOREACH_8(fn, x, ...) fn(x), DBG_FOREACH_7(fn, __VA_ARGS__)
745+
#define DBG_FOREACH_9(fn, x, ...) fn(x), DBG_FOREACH_8(fn, __VA_ARGS__)
746+
#define DBG_FOREACH_10(fn, x, ...) fn(x), DBG_FOREACH_9(fn, __VA_ARGS__)
747+
#define DBG_FOREACH_11(fn, x, ...) fn(x), DBG_FOREACH_10(fn, __VA_ARGS__)
748+
#define DBG_FOREACH_12(fn, x, ...) fn(x), DBG_FOREACH_11(fn, __VA_ARGS__)
749+
#define DBG_FOREACH_13(fn, x, ...) fn(x), DBG_FOREACH_12(fn, __VA_ARGS__)
750+
#define DBG_FOREACH_14(fn, x, ...) fn(x), DBG_FOREACH_13(fn, __VA_ARGS__)
751+
#define DBG_FOREACH_15(fn, x, ...) fn(x), DBG_FOREACH_14(fn, __VA_ARGS__)
752+
#define DBG_FOREACH_16(fn, x, ...) fn(x), DBG_FOREACH_15(fn, __VA_ARGS__)
753+
#define DBG_FOREACH_N(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, \
754+
_14, _15, _16, N, ...) \
755+
N
756+
757+
#define DBG_FOREACH(fn, ...) \
758+
DBG_FOREACH_N(__VA_ARGS__, DBG_FOREACH_16, DBG_FOREACH_15, DBG_FOREACH_14, \
759+
DBG_FOREACH_13, DBG_FOREACH_12, DBG_FOREACH_11, \
760+
DBG_FOREACH_10, DBG_FOREACH_9, DBG_FOREACH_8, DBG_FOREACH_7, \
761+
DBG_FOREACH_6, DBG_FOREACH_5, DBG_FOREACH_4, DBG_FOREACH_3, \
762+
DBG_FOREACH_2, DBG_FOREACH_1, unused) \
763+
(fn, __VA_ARGS__)
764+
765+
#define DBG_STRINGIFY_IMPL(x) #x
766+
#define DBG_STRINGIFY(x) DBG_STRINGIFY_IMPL(x)
767+
768+
#define dbg(...) \
769+
dbg::DebugOutput(__FILE__, __LINE__, __func__) \
770+
.print({DBG_FOREACH(DBG_STRINGIFY, __VA_ARGS__)}, __VA_ARGS__)
707771
#else
708772
#define dbg(...) dbg::identity(__VA_ARGS__)
709773
#endif // DBG_MACRO_DISABLE

tests/basic.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,36 @@ TEST_CASE("side effects") {
3939
CHECK(x == 2);
4040
}
4141

42+
TEST_CASE("multiple arguments") {
43+
SECTION("output format") {
44+
// The output of dbg(x, y) should be same as dbg(x); dbg(y).
45+
std::stringstream ss;
46+
const auto orig_buf = std::cerr.rdbuf(ss.rdbuf());
47+
// Put multiple statements in the same line to get exactly same output.
48+
// clang-format off
49+
dbg(42); dbg("test"); dbg(42, "test");
50+
// clang-format on
51+
std::cerr.rdbuf(orig_buf);
52+
53+
std::string lines[4];
54+
for (int i = 0; i < 4; i++) {
55+
std::getline(ss, lines[i]);
56+
}
57+
CHECK(lines[0] == lines[2]); // output for 42
58+
CHECK(lines[1] == lines[3]); // output for "test"
59+
}
60+
61+
SECTION("expression") {
62+
// It should return the last expression.
63+
int x = dbg(1, 2, 1 + 2);
64+
CHECK(x == 3);
65+
66+
// Wrap unprotected commas with parenthesis.
67+
x = dbg(1, (std::vector<int>{2, 3, 4}), 5);
68+
CHECK(x == 5);
69+
}
70+
}
71+
4272
TEST_CASE("pretty_print") {
4373
SECTION("primitive types") {
4474
CHECK(pretty_print(3) == "3");

tests/demo.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ int main() {
6363
dbg(9 + 33);
6464
dbg(test_string + " world");
6565

66+
dbg("====== multiple arguments");
67+
68+
dbg(test_int, (std::vector<int>{2, 3, 4}), test_string);
69+
6670
dbg("====== containers");
6771

6872
const std::vector<int> dummy_vec_int{3, 2, 3};

0 commit comments

Comments
 (0)