Browse code

Add implementation

Robert Cranston authored on 18/05/2020 13:08:47
Showing 3 changed files

... ...
@@ -2,10 +2,158 @@
2 2
 
3 3
 A [C++11][] [`std::string`][] [macro][] library.
4 4
 
5
+If [C++20][] is avaiable, [`std::format`][] and [`std::source_location`][] may
6
+be more appropriate. If [C++23][] is available, [`std::stacktrace`][] may be
7
+more appropriate.
8
+
5 9
 [`cxx-str`]: https://git.rcrnstn.net/rcrnstn/cxx-str
6 10
 [C++11]: https://en.wikipedia.org/wiki/C++11
7 11
 [`std::string`]: https://en.cppreference.com/w/cpp/string/basic_string
8 12
 [macro]: https://en.cppreference.com/w/cpp/preprocessor/replace
13
+[C++20]: https://en.wikipedia.org/wiki/C++20
14
+[`std::format`]: https://en.cppreference.com/w/cpp/utility/format/format
15
+[`std::source_location`]: https://en.cppreference.com/w/cpp/utility/source_location
16
+[C++23]: https://en.wikipedia.org/wiki/C++23
17
+[`std::stacktrace`]: https://en.cppreference.com/w/cpp/utility/basic_stacktrace
18
+
19
+## Usage
20
+
21
+Note that macros can result in unintuitive behavior.
22
+
23
+Looking at the [source][] and [test][] is the best way to understand usage.
24
+
25
+Throughout, arguments are only evaluated once. When [lambda][]s are defined and
26
+immediately called behind the scenes, values are implicitly [capture][]d by
27
+reference. The only lambda that implicitly captures by value is
28
+`STR_FUNC(VALUE)`.
29
+
30
+Include the header:
31
+
32
+```cpp
33
+#include <str.hpp>
34
+```
35
+
36
+There are a few different categories of functionality:
37
+
38
+-   Easy access to [`std::ostringstream`][]'s [`operator<<`][] and [`str()`][].
39
+    The `VALUE` expressions below can contain several values joined with `<<`.
40
+
41
+    `STR(VALUE)` produces a string.
42
+
43
+    `STR_FUNC(VALUE)` produces a [lambda][], which implicitly [capture][]s by
44
+    value, and returns a string. Useful for lazy evaluation of costly string
45
+    operations. For good performance, make sure the standard library can
46
+    perform Small String Optimization ([SSO][]) (and if used with
47
+    [`std::function`][], Small Object Optimization ([SOO][])).
48
+
49
+    `STR_JOIN(JOIN, ITERATOR, VALUE, ...)` makes the variable name `ITERATOR`
50
+    available in the `VALUE` expression, and joins the corresponding non-empty
51
+    (as interpreted by `STR(VALUE)`) elements of `...` with `JOIN`.
52
+
53
+    `STR_JOIN_INIT(JOIN, ITERATOR, VALUE, TYPE, ...)` forwards to
54
+    `STR_JOIN(JOIN, ITERATOR, VALUE, ...)`, passing
55
+    [`std::initializer_list`][]`<TYPE>`[`__VA_ARGS__`][]`)` as the last
56
+    argument. Notice the lack of braces, they have to be supplied manually.
57
+
58
+    `STR_JOIN_INITSTR(JOIN, ITERATOR, VALUE, ...)` forwards to `STR_JOIN_INIT`
59
+    with `std::string` as the `TYPE` argument.
60
+
61
+-   Easy access to the stringifying and concatenating [`#` and `##`
62
+    operators][] to help map constants to strings.
63
+
64
+    `STR_CASE(CASE)` uses `case` and `return` statements: `case (CASE): return
65
+    (#CASE);`.
66
+
67
+    `STR_COND(VALUE, CASE)` uses the [conditional operator][]: `((VALUE) ==
68
+    (CASE)) ? (#CASE) :`.
69
+
70
+    `STR_INIT(CASE)` uses [list initialization][]: `{(CASE), (#CASE)},`.
71
+
72
+    `STR_ARGS(ARG)` expands to `ARG, #ARG`.
73
+
74
+    `STR_ARGS_PREFIX(PREFIX, ARG)` expands to `PREFIX##ARG, #ARG`.
75
+
76
+-   Easy access to source file and line.
77
+
78
+    `STR_HERE(VALUE)` joins [`__FILE__` and `__LINE__`][] and [`__func__`][]
79
+    with `":"`, and appends `": "` and the given argument to produce a string.
80
+
81
+-   Easy handling to ([nested][]) [`std::exception`][]s, which contain strings
82
+    accessible through the [`what()`][] method.
83
+
84
+    `STR_EXCEPTION` can be defined to control what exceptions are thrown by the
85
+    macros below. If it is not defined, [`std::runtime_error`][] is used.
86
+
87
+    `STR_THROW(VALUE)` [`throw`][]s a `STR_EXCEPTION` initialized with
88
+    `STR(VALUE)`.
89
+
90
+    `STR_THROW_ERRNO()` calls `STR_THROW` with the argument
91
+    `std::generic_category().message(errno) << "."`.
92
+
93
+    `STR_RETHROW(VALUE)` calls [`std::terminate`][] if called outside a
94
+    [`catch`][] block. Otherwise, `STR_THROW`s `VALUE` followed by the
95
+    [`what()`][] of the current exception (so it is appropriate to include a
96
+    separator manually) if it is a (derives from) [`std::exception`][],
97
+    otherwise simply rethrows the current exception.
98
+
99
+    `STR_NESTED_THROW(VALUE)` calls [`std::throw_with_nested`][] on a
100
+    `STR_EXCEPTION` initialized with `STR(VALUE)`.
101
+
102
+    `STR_NESTED_THROW_ERRNO()` calls `STR_NESTED_THROW` with the argument
103
+    `std::generic_category().message(errno) << "."`.
104
+
105
+    `STR_NESTED_WHAT(JOIN, EXCEPTION)` joins the strings returned by the
106
+    (optionally) [nested][] [`std::exception`][]-based `EXCEPTION`'s
107
+    [`what()`][] methods with `JOIN`, and returns the resulting string.
108
+
109
+[source]: include/str.hpp
110
+[test]: tests/str.cpp
111
+[lambda]: https://en.cppreference.com/w/cpp/language/lambda
112
+[capture]: https://en.cppreference.com/w/cpp/language/lambda#Lambda_capture
113
+[`std::ostringstream`]: https://en.cppreference.com/w/cpp/io/basic_ostringstream
114
+[`operator<<`]: https://en.cppreference.com/w/cpp/io/basic_ostream/operator_ltlt
115
+[`str()`]: https://en.cppreference.com/w/cpp/io/basic_ostringstream/str
116
+[SSO]: https://en.cppreference.com/w/cpp/language/acronyms
117
+[`std::function`]: https://en.cppreference.com/w/cpp/utility/functional/function
118
+[SOO]: https://en.cppreference.com/w/cpp/language/acronyms
119
+[`std::initializer_list`]: https://en.cppreference.com/w/cpp/utility/initializer_list
120
+[`__VA_ARGS__`]: https://en.cppreference.com/w/cpp/preprocessor/replace
121
+[`#` and `##` operators]: https://en.cppreference.com/w/cpp/preprocessor/replace#.23_and_.23.23_operators
122
+[list initialization]: https://en.cppreference.com/w/cpp/language/list_initialization
123
+[conditional operator]: https://en.cppreference.com/w/cpp/language/operator_other#Conditional_operator
124
+[`__FILE__` and `__LINE__`]: https://en.cppreference.com/w/c/preprocessor/line
125
+[`__func__`]: https://en.cppreference.com/w/cpp/language/function#func
126
+[`#ifndef`]: https://en.cppreference.com/w/cpp/preprocessor/conditional
127
+[`NDEBUG`]: https://en.cppreference.com/w/c/error/assert
128
+[nested]: https://en.cppreference.com/w/cpp/error/nested_exception
129
+[`std::exception`]: https://en.cppreference.com/w/cpp/error/exception
130
+[`what()`]: https://en.cppreference.com/w/cpp/error/exception/what
131
+[`std::runtime_error`]: https://en.cppreference.com/w/cpp/error/runtime_error
132
+[`throw`]: https://en.cppreference.com/w/cpp/language/throw
133
+[`std::terminate`]: https://en.cppreference.com/w/cpp/error/terminate
134
+[`catch`]: https://en.cppreference.com/w/cpp/language/try_catch
135
+[`std::throw_with_nested`]: https://en.cppreference.com/w/cpp/error/throw_with_nested
136
+
137
+## Test output
138
+
139
+The [test][] outputs:
140
+
141
+```
142
+0x3
143
+0x3
144
+"first", "", "third"
145
+first, third
146
+
147
+first
148
+unknown
149
+third
150
+
151
+/path/to/cxx-str/tests/str.cpp:96: Hello from here
152
+
153
+Failed to do outer:
154
+Failed to do inner:
155
+Expected this, got that.
156
+```
9 157
 
10 158
 ## Building
11 159
 
... ...
@@ -0,0 +1,120 @@
1
+#ifndef STR_HPP_
2
+#define STR_HPP_
3
+
4
+
5
+#include <cerrno>
6
+#include <exception>
7
+#include <functional>
8
+#include <initializer_list>
9
+#include <sstream>
10
+#include <stdexcept>
11
+#include <string>
12
+#include <system_error>
13
+
14
+
15
+#define STR(VALUE) \
16
+    (((std::ostringstream &)(std::ostringstream{} << VALUE)).str())
17
+
18
+#define STR_FUNC(VALUE) \
19
+    ([=]() { return STR(VALUE); })
20
+
21
+#define STR_JOIN(JOIN, ITERATOR, VALUE, ...) \
22
+    ([&]() \
23
+    { \
24
+        auto const & str_join_  = JOIN; \
25
+        auto         str_oss_   = std::ostringstream{}; \
26
+        auto         str_first_ = true; \
27
+        for (auto const & ITERATOR : __VA_ARGS__) \
28
+        { \
29
+            auto str_value_ = STR(VALUE); \
30
+            if (str_value_.empty()) \
31
+                continue; \
32
+            if (!str_first_) \
33
+                str_oss_ << str_join_; \
34
+            str_first_ = false; \
35
+            str_oss_ << str_value_; \
36
+        } \
37
+        return str_oss_.str(); \
38
+    }())
39
+
40
+#define STR_JOIN_INIT(JOIN, ITERATOR, VALUE, TYPE, ...) \
41
+    STR_JOIN(JOIN, ITERATOR, VALUE, std::initializer_list<TYPE>__VA_ARGS__)
42
+
43
+#define STR_JOIN_INITSTR(JOIN, ITERATOR, VALUE, ...) \
44
+    STR_JOIN_INIT(JOIN, ITERATOR, VALUE, std::string, __VA_ARGS__)
45
+
46
+
47
+#define STR_CASE(CASE) \
48
+    case (CASE): return (#CASE);
49
+
50
+#define STR_COND(VALUE, CASE) \
51
+    ((VALUE) == (CASE)) ? (#CASE) :
52
+
53
+#define STR_INIT(CASE) \
54
+    {(CASE), (#CASE)},
55
+
56
+#define STR_ARGS(ARG) \
57
+    ARG, #ARG
58
+
59
+#define STR_ARGS_PREFIX(PREFIX, ARG) \
60
+    PREFIX##ARG, #ARG
61
+
62
+
63
+#define STR_HERE(VALUE) \
64
+    STR(__FILE__ << ":" << __LINE__ << ":" << __func__ << ": " << VALUE)
65
+
66
+
67
+#ifndef STR_EXCEPTION
68
+    #define STR_EXCEPTION std::runtime_error
69
+#endif
70
+
71
+#define STR_THROW(VALUE) \
72
+    (throw STR_EXCEPTION{STR(VALUE)})
73
+
74
+#define STR_THROW_ERRNO() \
75
+    STR_THROW(std::generic_category().message(errno) << ".")
76
+
77
+#define STR_RETHROW(VALUE) \
78
+    try \
79
+    { \
80
+        throw; \
81
+    } \
82
+    catch (std::exception const & str_exception_) \
83
+    { \
84
+        STR_THROW(VALUE << str_exception_.what()); \
85
+    }
86
+
87
+#define STR_NESTED_THROW(VALUE) \
88
+    (std::throw_with_nested(STR_EXCEPTION{STR(VALUE)}))
89
+
90
+#define STR_NESTED_THROW_ERRNO() \
91
+    STR_NESTED_THROW(std::generic_category().message(errno) << ".")
92
+
93
+#define STR_NESTED_WHAT(JOIN, EXCEPTION) \
94
+    ([&]() \
95
+    { \
96
+        auto const & str_join_   = JOIN; \
97
+        auto         str_oss_    = std::ostringstream{}; \
98
+        auto         str_first_  = true; \
99
+        auto str_what_ = std::function<void(std::exception const &)>{}; \
100
+        str_what_ = [&](std::exception const & str_exception_) \
101
+        { \
102
+            if (!str_first_) \
103
+                str_oss_ << str_join_; \
104
+            str_first_ = false; \
105
+            str_oss_ << str_exception_.what(); \
106
+            try \
107
+            { \
108
+                std::rethrow_if_nested(str_exception_); \
109
+            } \
110
+            catch (std::exception const & str_exception_nested_) \
111
+            { \
112
+                str_what_(str_exception_nested_); \
113
+            } \
114
+        }; \
115
+        str_what_(EXCEPTION); \
116
+        return str_oss_.str(); \
117
+    }())
118
+
119
+
120
+#endif // STR_HPP_
0 121
new file mode 100644
... ...
@@ -0,0 +1,121 @@
1
+#include <str.hpp>
2
+
3
+#include <iostream>
4
+#include <string>
5
+#include <unordered_map>
6
+#include <vector>
7
+
8
+
9
+auto constexpr first  = 1;
10
+auto constexpr second = 2;
11
+auto constexpr third  = 3;
12
+
13
+std::string static str_case_(int value)
14
+{
15
+    switch(value)
16
+    {
17
+        STR_CASE(first)
18
+        STR_CASE(second)
19
+        STR_CASE(third)
20
+        default:
21
+            return "unknown";
22
+    }
23
+}
24
+
25
+std::string static str_cond_(int value)
26
+{
27
+    return
28
+        STR_COND(value, first)
29
+        STR_COND(value, second)
30
+        STR_COND(value, third)
31
+        "unknown";
32
+}
33
+
34
+std::string static str_init_(int value)
35
+{
36
+    auto str = std::unordered_map<int, std::string>{
37
+        STR_INIT(first)
38
+        STR_INIT(second)
39
+        STR_INIT(third)
40
+    }[value];
41
+    if (str.empty())
42
+        return "unknown";
43
+    return str;
44
+}
45
+
46
+
47
+void static func_inner_()
48
+{
49
+    try
50
+    {
51
+        STR_THROW(
52
+            "Expected " << "this" << ", " <<
53
+            "got "      << "that" << "."
54
+        );
55
+    }
56
+    catch (...)
57
+    {
58
+        STR_NESTED_THROW("Failed to do " << "inner" << ":");
59
+    }
60
+}
61
+
62
+void static func_outer_()
63
+{
64
+    try
65
+    {
66
+        func_inner_();
67
+    }
68
+    catch (...)
69
+    {
70
+        STR_NESTED_THROW("Failed to do " << "outer" << ":");
71
+    }
72
+}
73
+
74
+
75
+int main()
76
+{
77
+    auto const value  = 0x03;
78
+    auto const values = std::vector<std::string>{"first", "", "third"};
79
+    std::cout << STR(     std::hex << std::showbase << value)   << std::endl;
80
+    std::cout << STR_FUNC(std::hex << std::showbase << value)() << std::endl;
81
+    std::cout << STR_JOIN(", ", it, "\"" << it << "\"", values) << std::endl;
82
+    std::cout << STR_JOIN_INIT(", ", it, it, int, {1, 2, 3}) << std::endl;
83
+    std::cout << STR_JOIN_INITSTR(", ", it, it, {
84
+        std::string{"first"},
85
+        "",
86
+        "third",
87
+    }) << std::endl;
88
+
89
+    std::cout << std::endl;
90
+
91
+    std::cout << str_case_(1) << std::endl; // Uses `STR_CASE`.
92
+    std::cout << str_cond_(0) << std::endl; // Uses `STR_COND`.
93
+    std::cout << str_init_(3) << std::endl; // Uses `STR_INIT`.
94
+
95
+    std::cout << std::endl;
96
+
97
+    std::cout << STR_HERE("Hello from " << "here") << std::endl;
98
+
99
+    std::cout << std::endl;
100
+
101
+    try
102
+    {
103
+        STR_THROW_ERRNO();
104
+    }
105
+    catch (std::exception const & exception)
106
+    {
107
+        std::cout << exception.what() << std::endl;
108
+    }
109
+
110
+    try
111
+    {
112
+        func_outer_();
113
+    }
114
+    catch (std::exception const & exception)
115
+    {
116
+        // TODO(rcrnstn): There may be a bug in `clang-tidy` 13, check if
117
+        // `STR_NESTED_WHAT` passes with a newer version.
118
+        // NOLINTNEXTLINE
119
+        std::cout << STR_NESTED_WHAT("\n", exception) << std::endl;
120
+    }
121
+}