Skip to content

Commit 6dbf940

Browse files
authored
Update [Callables][C++_vs_Java].md
1 parent 4ace546 commit 6dbf940

File tree

1 file changed

+135
-107
lines changed

1 file changed

+135
-107
lines changed

docs/basics/[Callables][C++_vs_Java].md

Lines changed: 135 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -12,59 +12,67 @@ Let's see how we can accomplish this task in both languages, and what parallels
1212
## C++ approach
1313

1414
In C++ this is quite straight forward: you just need to specify the *signature *that function has to satisfy
15-
16-
template **\<**typename R**&gt;**
17-
**using ****callable_type ****= **R **(*)(**std**::**string**);**
15+
16+
```c++
17+
template <typename R>
18+
using callable_type = R (*)(std::string);
19+
```
1820

1921
where the *callable_type* defines the signature of the **free function**.
20-
2122
At the client side
22-
23-
**void ****func****(**std**::**vector**\<**std**::**string**&gt;** input**, ****callable_type****\<****void****&gt; **callback**) {**
24-
    **for ****(****const **auto**&amp;** in **:** input**) {**
25-
        callback**(**in**);**
23+
24+
```c++
25+
void func(std::vector<std::string> input, callable_type<void> callback) {
26+
    for (const auto in : input) {
27+
        callback (in);
2628
        // std::invoke(callback, in);
27-
    **}**
28-
**}**
29-
29+
    }
30+
}
31+
```
3032
our higher-order function can lift this - visiting all elements in array and applying the same functionality accordingly.
3133
This way, our callable type becomes the *customization poin*t (Strategy Design Pattern): any callable that
3234
satisfies the signature, can be applied (!)
3335
3436
If we want to restrict to the *non-static member function* of a particular UDT, we can redefine it as
35-
36-
template **\<**typename R**, **class T**&gt;**
37-
**using ****callable_type ****= **R **(**T**::*)(**std**::**string**);**
38-
39-
@note ***std::function*** is universal, polymorphic placeholder for any callable: but this is out of the scope right now.
40-
41-
template **\<**class T**&gt;**
42-
**void ****func****(**std**::**vector**\<**std**::**string**&gt;** input**, ****callable_type****\<****void****&gt;** callback**, **T**&amp;** obj**) {**
43-
    **for ****(****const **auto**&amp;** in **:** input**) {**
44-
        **(**obj**.***callback**)(**in**);**
45-
// (ptr-&gt;*callback)(in); // in case that we pass the pointer
37+
38+
```c++
39+
template <ypename R, class T>
40+
using callable_type = R (T::)(std::string);
41+
```
42+
@note std::function is universal, polymorphic placeholder for any callable: but this is out of the scope right now.
43+
44+
```c++
45+
template <class T>
46+
void func(std::vector<std::string> input, callable_type<void> callback, T& obj) {
47+
    for (const auto& in : input) {
48+
        (obj.*callback)(in);
49+
// (ptr->*callback)(in); // in case that we pass the pointer
4650
        // std::invoke(callback, obj, in);
47-
   **}**
48-
**}**
51+
   }
52+
}
53+
```
4954
5055
Back to signature.
5156
It becomes even more obvious using the *std::invoke* utility function, that the instance of the UDT needs to be the
5257
very first argument of any non-static member function invocation.
5358
Welcome to the world of OO programming.
5459
55-
###
5660
### Variadic arguments pack
5761
5862
Where C++ prevails is that with C++ we can specify really generic: universal function signature,
5963
with arbitrary number of arguments - even of a different type, using **variadic arguments** pack
60-
61-
template **\<**typename R**, **typename**...**Args**&gt;**
62-
**using ****universal_callback_type ****= **R **(*)(**Args**&amp;&amp;...****);**
64+
65+
```c++
66+
template <typename R, typename...Args>
67+
using universal_callback_type = R (*)(Args&&...);
68+
```
6369

6470
@note We can also add the *const qualifier* to signature - to make the function's enclosing type T immutable
65-
66-
template **\<**typename R**, **typename T**,** typename**...**Args**&gt;**
67-
**using ****universal_callback_type ****= **R **(**T**::*****)(****const** Args**&amp;&amp;...) ****const****;**
71+
72+
```c++
73+
template <typename R, typename T, typename...Args>
74+
using universal_callback_type = R (T::*)(const Args&&...) const;
75+
```
6876

6977
@note Actually, we can add *volatile* qualifier as well - which means, that the function will be called on the
7078
volatile instance of the enclosing class T
@@ -73,17 +81,21 @@ volatile instance of the enclosing class T
7381

7482
In C++ - one can also specify explicitly - as part of the function signature, whether
7583
the function may throw
76-
77-
template **\<**typename R**&gt;**
78-
**using ****callback_type ****= **R **(*)(****void****) ****throw ****(**std**::**logic_error**);**
84+
85+
```c++
86+
template <typename R>
87+
using callback_type = R (*)(void) throw (std::logic_error);
88+
```
7989

8090
Starting with C++11 - this is considered deprecated.
8191
Instead - assuming that every function (except destructor) can implicitly throw, as a hint to compiler
8292
there is a new operator ***noexcept ***that indicates whether the function may throw - or not
8393
(noexcept == noexcept(true)): it can be conditionally expressed
84-
85-
template **\<**typename R**,** typename Arg**&gt;**
86-
**using ****callback_type ****= **R **(*)(**Arg**) ****noexcept ****(**std**::**is_nothrow_copy_constructible_v**\<**Arg**&gt;);**
94+
95+
```c++
96+
template <typename R, typename...Args>
97+
using callback_type = R (*)(Arg&&...) noexcept (std::is_nothrow_copy_constructible_v<Args> &&...);
98+
```
8799

88100
As a consequence - there will be no stack unwinding in order to propagate the exception to the
89101
outer functions that presumably catch and handle exception: but rather if the exception is thrown - the program
@@ -96,57 +108,68 @@ So, how we can accomplish the same with Java?
96108
And Java is indeed the pure OO language.
97109

98110
In Java, we can specify a custom **callback interface**
99-
111+
112+
```java
100113
@FunctionalInterface
101-
**interface ****CallbableType****\<**R, T**&gt; {**
102-
    R apply**(T obj);**
103-
**}**
114+
interface CallbableType<R, T> {
115+
    R apply(T obj);
116+
}
117+
```
104118

105119
This would be equivalent to defining the non-static member function of T, that is **parameterless** - since the very first argument
106120
must be the instance on which the method will be invoked.
107121

108122
Then, we define the higher-order function as
109-
110-
**\<**R, T**&gt;** R **func****(**@NonNull **CallableType****\<**R, T**&gt; **callback, T obj**) {**
123+
124+
```java
125+
<R, T> R func(@NonNull CallableType <R, T> callback, T obj) {
111126
    // do something
112-
    **return **callback**.**apply**(**obj**);**
113-
**}**
127+
    return callback.apply(obj);
128+
}
129+
```
114130

115131
This is similar calling the std::invoke, providing the instance of T, as a first argument
116132

117133
We can, on the place where *callback* is expected, provide:
118-
>- Reference to the non-static member function of the enclosing class
119-
>**this****::\<**function**&gt;**
120-
>- Reference to the non-static member function of another class, for which we need to provide argument as well
121-
>**\<**Class**&gt;::\<**function**&gt;**
122-
>- We can provide the **lambda** object on the fly, that satisfies the signature
123-
>**(**obj**)-&gt; {**
124-
>    // do something
125-
>    **return** obj**.\<**func**&gt;();**
126-
>**}**
127-
>
128-
134+
- Reference to the non-static member function of the enclosing class
135+
```java
136+
this::<function>
137+
```
138+
- Reference to the non-static member function of another class, for which we need to provide argument as well
139+
```java
140+
<Class>::<function>
141+
```
142+
- We can provide the lambda object on the fly, that satisfies the signature
143+
144+
```java
145+
(obj)-> {
146+
     // do something
147+
return obj.<func>();
148+
}
149+
```
129150
Pay attention - we don't explicitly implement the functional interface: we use it as a *placeholder* for providing
130151
the already existing callables that satisfy the signature
131-
132-
**private ****\<**R**&gt;** List**\<**R**&gt; ****transform****(**@NonNull List**\<**Person**&gt;** list**, **@NonNull **CallableType****\<**R**,** Person**&gt;** callable**) {**
133-
    **return** list**.**stream**().**map**(****callable****::****apply****).**collect**(**toList**());**
134-
**}**
135-
136-
@Test
137-
**public void **testTransformPersonToName**() {**
138-
    List**\<**Person**&gt;** people **=** List**.**of**(****new **Person**(**"Alice"**, ****25****), ****new **Person**(**"Bob"**, ****30****));**
139-
140-
    transform**(**people**, **Person**::**getName**).**forEach**(**System**.**out**::**println**); **// Reference to method
141-
    transform**(**people**, **person**-&gt;**person**.**getAge**()****).**forEach**(**System**.**out**::**println**); **// Lambda expression
142-
**}**
152+
153+
```java
154+
private <R> List<R> transform(@NonNull List<Person> list, @NonNull CallableType<R, Person> callable) {
155+
    return list.stream().map(callable::apply).collect(toList());
156+
}
157+
158+
@Test
159+
public void testTransformPersonToName() {
160+
    List<Person> people = List.of(new Person("Alice", 25), new Person("Bob", 30));
161+
162+
    transform(people, Person::getName).forEach(System.out::println); // Reference to method
163+
    transform(people, person -> person.getAge()).forEach(System.out::println); // Lambda expression
164+
}
165+
```
143166

144167
@note C++ introduced the *ranges* in C++20. Java has *streams* since Java 8 SDK.
145168

146169
As matter of fact - there are already predefined* Functional Interfaces* which are part of the *java.util.function *package,
147170
that is introduced with Java 8 SDK.
148171

149-
*Function\<R, T&gt; *offers the same *apply*() callback as our manually written interface.
172+
Function<R, T> offers the same apply() callback as our manually written interface.
150173

151174
Actually, it's callbacks interface - since it provides the signature for three additional callbacks.
152175
It's even composable, since you can instantiate it with the callable that will be applied first, and
@@ -158,54 +181,59 @@ the functional programming style in Java.
158181
### Exception in signature
159182

160183
Interesting enough, the Exception Type can be also part of the function signature in Java
161-
**
162-
@FunctionalInterface
163-
**interface ****CallableType****\<**R**,** T**&gt; {**
164-
    R apply**(**T obj**) ****throws **RemoteException**;**
165-
**}**
166-
184+
185+
```java
186+
@FunctionalInterface
187+
interface CallableType<R, T> {
188+
    R apply(T obj) throws RemoteException;
189+
}
190+
```
167191
or to be generic as possible
168-
169-
@FunctionalInterface
170-
**interface ****CallableType****\<**R**,** T**,** E **extends** Exception**&gt; {**
171-
    R apply**(**@NonNull T obj**) ****throws** E**;**
172-
**}**
173-
192+
193+
```java
194+
@FunctionalInterface
195+
interface CallableType<R, T, E extends Exception> {
196+
    R apply(@NonNull T obj) throws E;
197+
}
198+
```
174199
Our higher-order function may preserve the exception indication in its own signature
175-
176-
**\<**T**,** R**,** E **extends** Exception**&gt;** R **invoke****(****CallableType****\<**T**,** R**,** E**&gt;** f**,** T arg**) ****throws** E **{**
177-
    // do something
178-
    **return** f**.**apply**(**arg**);**
179-
**}**
180-
200+
201+
```java
202+
<T, R, E extends Exception> R invoke(CallableType<T, R, E> f, T arg) throws E {
203+
    // do something
204+
    return f.apply(arg);
205+
}
206+
```
181207
or it can handle it internally.
182208

183209
In case that due to *interoperability* - the API expects the Function Interface which is not throwable, and we use the one which may throw, we can write the safe wrapper to bridge it
184-
185-
**static ****\<**T**,** R**&gt; ****Function****\<**T**,** R**&gt; ****safeWrap****(****ThrowingFunction****\<**T**,** R**,** ?**&gt;** f**) {**
186-
    **return** t **-&gt; {**
187-
        **try ****{**
188-
                **return** f**.**apply**(**t**);**
189-
            **} ****catch ****(**RuntimeException e**) {**
190-
                **throw** e**; **// Re-throw unchecked exceptions
191-
            **} ****catch ****(**Exception e**) {**
192-
                **throw new **IllegalStateException**(**"Unexpected checked exception"**,** e**);**
193-
            **}**
194-
   **};**
195-
**}**
196-
210+
211+
```java
212+
static <T, R> Function<T, R> safeWrapper(ThrowingFunction<T, R, ?> f) {
213+
    return t -> {
214+
        try{
215+
             return f.apply(t);
216+
           } catch (RuntimeException e) {
217+
                throw e; // Re-throw unchecked exceptions
218+
           } catch (Exception e) {
219+
                throw new IllegalStateException("Unexpected checked exception", e);
220+
           }
221+
   };
222+
}
223+
```
197224

198225
### Variadic arguments pack
199226

200227
Unlike C++ - Java doesn't support variadic arguments pack, at least not for expressing the arbitrary number of
201228
heterogenous types.
202229
It supports only variadic arguments list of the same type
203-
230+
231+
```java
204232
@FunctionalInterface
205-
**interface ****CallableType****\<**R**,** T**,** E **extends** Exception**&gt; {**
206-
    R apply**(**@NonNull T**...** objs**) ****throws** E**;**
207-
**}**
208-
233+
interface CallableType<R, T, E extends Exception> {
234+
    R apply(@NonNull T...objs) throws E;
235+
}
236+
```
209237

210238
## Conclusion
211239

@@ -214,8 +242,8 @@ programming was not originally part of the language core - it's added afterwards
214242
*type erasur*e (stripping all type information - and treating it as Object inside the function template).
215243

216244
More on **type erasure**, and different interpretations - in different languages:
217-
>- [https://github.com/damirlj/modern_cpp_tutorials?tab=readme-ov-file#tut7](https://github.com/damirlj/modern_cpp_tutorials?tab=readme-ov-file#tut7)
218-
>
245+
[Type Erasure](https://github.com/damirlj/modern_cpp_tutorials?tab=readme-ov-file#tut7)
246+
219247

220248
The only where Java is inferior in fulfilling this particular task - is the fact that heterogenous variadic argument pack is not
221249
supported (nor the fold expressions, type-traits, auto type deduction and all other mechanics of template metaprogramming).

0 commit comments

Comments
 (0)