Skip to content

Commit bdcdcd3

Browse files
committed
Deduce argument size and types from lambda signature
This commit adds an ability to use lambda in a more traditional scenario with a lambda and a predefined number of arguments. This commit reuses `add_callback` function to extend its functionality. It reuses existing methods and should provide backward compatibility with the existing use cases.
1 parent 18ad33a commit bdcdcd3

File tree

4 files changed

+97
-15
lines changed

4 files changed

+97
-15
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.coveralls.yml
2+
.cache/
23
.vscode
34
.vs
45

include/inja/environment.hpp

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,17 @@
77
#include <sstream>
88
#include <string>
99
#include <string_view>
10+
#include <tuple>
11+
#include <type_traits>
1012

11-
#include "json.hpp"
1213
#include "config.hpp"
1314
#include "function_storage.hpp"
15+
#include "json.hpp"
1416
#include "parser.hpp"
1517
#include "renderer.hpp"
1618
#include "template.hpp"
1719
#include "throw.hpp"
20+
#include "utils.hpp"
1821

1922
namespace inja {
2023

@@ -25,6 +28,30 @@ class Environment {
2528
FunctionStorage function_storage;
2629
TemplateStorage template_storage;
2730

31+
template <class Arg>
32+
static Arg get_callback_argument(const Arguments &args, size_t index) {
33+
if constexpr (std::is_lvalue_reference_v<Arg>) {
34+
return args[index]->get_ref<const Arg &>();
35+
} else {
36+
return args[index]->get<Arg>();
37+
}
38+
}
39+
40+
template <class Ret, class Func, class... Args, size_t... Is>
41+
void add_callback_closure(const std::string &name, Func func,
42+
function_signature::ArgsList<Args...> /*args*/,
43+
std::index_sequence<Is...> /*seq*/) {
44+
add_callback(name, sizeof...(Args),
45+
[func = std::move(func)](const Arguments &args) -> json {
46+
if constexpr (std::is_same_v<Ret, void>) {
47+
func(get_callback_argument<Args>(args, Is)...);
48+
return {};
49+
} else {
50+
return func(get_callback_argument<Args>(args, Is)...);
51+
}
52+
});
53+
}
54+
2855
protected:
2956
LexerConfig lexer_config;
3057
ParserConfig parser_config;
@@ -179,10 +206,41 @@ class Environment {
179206
}
180207

181208
/*!
182-
@brief Adds a variadic callback
209+
@brief Adds a callback
183210
*/
184-
void add_callback(const std::string& name, const CallbackFunction& callback) {
185-
add_callback(name, -1, callback);
211+
template <class Callback>
212+
void add_callback(const std::string &name, Callback callback) {
213+
constexpr auto get_sig = [] {
214+
if constexpr (std::is_object_v<Callback>) {
215+
return function_signature::Get<decltype(&Callback::operator())> {};
216+
} else {
217+
return function_signature::Get<Callback>{};
218+
}
219+
};
220+
using Sig = decltype(get_sig());
221+
constexpr size_t num_args = std::tuple_size_v<typename Sig::ArgsTuple>;
222+
223+
constexpr auto is_arguments_vector = [] {
224+
if constexpr (num_args == 1) {
225+
return std::is_same_v<
226+
std::remove_cv_t<std::remove_reference_t<
227+
std::tuple_element_t<0, typename Sig::ArgsTuple>>>,
228+
Arguments>;
229+
} else {
230+
return false;
231+
}
232+
};
233+
234+
if constexpr (is_arguments_vector()) {
235+
// If callback has the only argument of `Arguments` - fallback to adding a
236+
// variadic callback
237+
add_callback(name, -1, callback);
238+
} else {
239+
// If it has other arguments - use it in a closure
240+
add_callback_closure<typename Sig::Ret>(
241+
name, std::move(callback), typename Sig::ArgsList{},
242+
std::make_index_sequence<num_args>{});
243+
}
186244
}
187245

188246
/*!

include/inja/utils.hpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,29 @@ inline bool starts_with(std::string_view view, std::string_view prefix) {
3131
}
3232
} // namespace string_view
3333

34+
namespace function_signature {
35+
template <class... Args> struct ArgsList {};
36+
template <class Func> struct Get {};
37+
template <class R, class... A> //
38+
struct Get<R(A...)> {
39+
using Ret = R;
40+
using ArgsList = ArgsList<A...>;
41+
using ArgsTuple = std::tuple<A...>;
42+
};
43+
template <class R, class C, class... A> //
44+
struct Get<R (C::*)(A...)> {
45+
using Ret = R;
46+
using ArgsList = ArgsList<A...>;
47+
using ArgsTuple = std::tuple<A...>;
48+
};
49+
template <class R, class C, class... A> //
50+
struct Get<R (C::*)(A...) const> {
51+
using Ret = R;
52+
using ArgsList = ArgsList<A...>;
53+
using ArgsTuple = std::tuple<A...>;
54+
};
55+
} // namespace function_signature
56+
3457
inline SourceLocation get_source_location(std::string_view content, size_t pos) {
3558
// Get line and offset position (starts at 1:1)
3659
auto sliced = string_view::slice(content, 0, pos);

test/test-functions.cpp

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -246,25 +246,24 @@ TEST_CASE("callbacks") {
246246
});
247247

248248
std::string greet = "Hello";
249-
env.add_callback("double-greetings", 0, [greet](inja::Arguments) { return greet + " " + greet + "!"; });
249+
env.add_callback("double-greetings",
250+
[greet] { return greet + " " + greet + "!"; });
250251

251252
env.add_callback("multiply", 2, [](inja::Arguments args) {
252253
double number1 = args.at(0)->get<double>();
253254
auto number2 = args.at(1)->get<double>();
254255
return number1 * number2;
255256
});
256257

257-
env.add_callback("multiply", 3, [](inja::Arguments args) {
258-
double number1 = args.at(0)->get<double>();
259-
double number2 = args.at(1)->get<double>();
260-
double number3 = args.at(2)->get<double>();
261-
return number1 * number2 * number3;
262-
});
258+
env.add_callback("multiply",
259+
[](double number1, double number2, double number3) {
260+
return number1 * number2 * number3;
261+
});
263262

264-
env.add_callback("length", 1, [](inja::Arguments args) {
265-
auto number1 = args.at(0)->get<std::string>();
266-
return number1.length();
267-
});
263+
env.add_callback("length",
264+
[](const std::string &number1) { return number1.length(); });
265+
266+
env.add_callback("log0", [] {});
268267

269268
env.add_void_callback("log", 1, [](inja::Arguments) {
270269

@@ -275,6 +274,7 @@ TEST_CASE("callbacks") {
275274
CHECK(env.render("{{ double(age) }}", data) == "56");
276275
CHECK(env.render("{{ half(age) }}", data) == "14");
277276
CHECK(env.render("{{ log(age) }}", data) == "");
277+
CHECK(env.render("{{ log0() }}", data) == "");
278278
CHECK(env.render("{{ double-greetings }}", data) == "Hello Hello!");
279279
CHECK(env.render("{{ double-greetings() }}", data) == "Hello Hello!");
280280
CHECK(env.render("{{ multiply(4, 5) }}", data) == "20.0");

0 commit comments

Comments
 (0)