Skip to content

Commit d0e7a1c

Browse files
Provide fully templated encoders and decoders (#5)
Co-authored-by: Jonatan Kłosko <[email protected]>
1 parent b3dee74 commit d0e7a1c

File tree

5 files changed

+132
-43
lines changed

5 files changed

+132
-43
lines changed

README.md

Lines changed: 57 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ NIFs in C++.
3232

3333
- STL compatible Erlang-backend mutex and rwlock.
3434

35+
- Compatible with STL container allocators and polymorphic memory
36+
resources.
37+
3538
## Motivation
3639

3740
Some projects make extensive use of NIFs, where using the C API results
@@ -131,28 +134,28 @@ auto message = fine::decode<std::string>(env, term);
131134

132135
Fine provides implementations for the following types:
133136

134-
| Type | Encoder | Decoder |
135-
| ------------------------------------ | ------- | ------- |
136-
| `fine::Term` | x | x |
137-
| `int64_t` | x | x |
138-
| `uint64_t` | x | x |
139-
| `double` | x | x |
140-
| `bool` | x | x |
141-
| `ErlNifPid` | x | x |
142-
| `ErlNifBinary` | x | x |
143-
| `std::string_view` | x | x |
144-
| `std::string` | x | x |
145-
| `fine::Atom` | x | x |
146-
| `std::nullopt_t` | x | |
147-
| `std::optional<T>` | x | x |
148-
| `std::variant<Args...>` | x | x |
149-
| `std::tuple<Args...>` | x | x |
150-
| `std::vector<T>` | x | x |
151-
| `std::map<K, V>` | x | x |
152-
| `fine::ResourcePtr<T>` | x | x |
153-
| `T` with [struct metadata](#structs) | x | x |
154-
| `fine::Ok<Args...>` | x | |
155-
| `fine::Error<Args...>` | x | |
137+
| C++ Type | Encoder | Decoder | Elixir Type |
138+
| ------------------------------------ | ------- | ------- | --------------------------- |
139+
| `fine::Term` | x | x | `term` |
140+
| `int64_t` | x | x | `integer` |
141+
| `uint64_t` | x | x | `non_neg_integer` |
142+
| `double` | x | x | `float` |
143+
| `bool` | x | x | `boolean` |
144+
| `ErlNifPid` | x | x | `pid` |
145+
| `ErlNifBinary` | x | x | `binary` |
146+
| `std::string_view` | x | x | `binary` |
147+
| `std::string` | x | x | `binary` |
148+
| `fine::Atom` | x | x | `atom` |
149+
| `std::nullopt_t` | x | | `nil` |
150+
| `std::optional<T>` | x | x | `a \| nil` |
151+
| `std::variant<Args...>` | x | x | `a \| b \| ... \| c` |
152+
| `std::tuple<Args...>` | x | x | `{a, b, ..., c}` |
153+
| `std::vector<T>` | x | x | `list(a)` |
154+
| `std::map<K, V>` | x | x | `%{k => v}` |
155+
| `fine::ResourcePtr<T>` | x | x | `reference` |
156+
| `T` with [struct metadata](#structs) | x | x | `%a{}` |
157+
| `fine::Ok<Args...>` | x | | `{:ok, ...}` |
158+
| `fine::Error<Args...>` | x | | `{:error, ...}` |
156159

157160
> #### ERL_NIF_TERM {: .warning}
158161
>
@@ -557,6 +560,38 @@ const char* my_object__name(struct my_object*);
557560
558561
fine::SharedMutex my_object_rwlock("my_lib", "my_object", my_object__name(my_object));
559562
```
563+
## Allocators
564+
565+
For compatibility with the STL, fine supports stateless allocators when
566+
decoding values, while also supporting stateful allocators when encoding
567+
values. The following shows how a custom `MyAllocator` allocator and
568+
`my_memory_resource` memory resource can be used in conjunction with fine:
569+
570+
```c++
571+
template<typename T>
572+
struct MyAllocator { ... };
573+
574+
std::pmr::memory_resource* my_memory_resource = ...;
575+
576+
std::vector<std::pmr::string, MyAllocator<std::pmr::string>> repeat_string(
577+
ErlNifEnv *,
578+
std::basic_string<char, std::char_traits<char>, MyAllocator<char>>
579+
string,
580+
std::uint64_t repeat) {
581+
std::vector<std::pmr::string, MyAllocator<std::pmr::string>> strings;
582+
583+
for (std::uint64_t i = 0; i != repeat; ++i) {
584+
strings.emplace_back(std::pmr::string(string, my_memory_resource));
585+
}
586+
587+
return strings;
588+
}
589+
FINE_NIF(repeat_string, 0);
590+
```
591+
592+
Attempting to decode STL containers making use of `std::pmr::polymorphic_allocator`
593+
will result in the `std::pmr::get_default_resource()` memory resource being
594+
used.
560595
561596
<!-- Docs -->
562597

c_include/fine.hpp

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -306,8 +306,7 @@ Term make_resource_binary(ErlNifEnv *env, ResourcePtr<T> resource,
306306
//
307307
// This is useful when returning large binary from a NIF and the source
308308
// buffer does not outlive the return.
309-
inline fine::Term make_new_binary(ErlNifEnv *env, const char *data,
310-
size_t size) {
309+
inline Term make_new_binary(ErlNifEnv *env, const char *data, size_t size) {
311310
ERL_NIF_TERM term;
312311
auto term_data = enif_make_new_binary(env, size, &term);
313312
if (term_data == nullptr) {
@@ -440,9 +439,12 @@ template <> struct Decoder<std::string_view> {
440439
}
441440
};
442441

443-
template <> struct Decoder<std::string> {
444-
static std::string decode(ErlNifEnv *env, const ERL_NIF_TERM &term) {
445-
return std::string(fine::decode<std::string_view>(env, term));
442+
template <typename Alloc>
443+
struct Decoder<std::basic_string<char, std::char_traits<char>, Alloc>> {
444+
using string = std::basic_string<char, std::char_traits<char>, Alloc>;
445+
446+
static string decode(ErlNifEnv *env, const ERL_NIF_TERM &term) {
447+
return string(fine::decode<std::string_view>(env, term));
446448
}
447449
};
448450

@@ -529,35 +531,38 @@ template <typename... Args> struct Decoder<std::tuple<Args...>> {
529531
}
530532
};
531533

532-
template <typename T> struct Decoder<std::vector<T>> {
533-
static std::vector<T> decode(ErlNifEnv *env, const ERL_NIF_TERM &term) {
534+
template <typename T, typename Alloc> struct Decoder<std::vector<T, Alloc>> {
535+
static std::vector<T, Alloc> decode(ErlNifEnv *env,
536+
const ERL_NIF_TERM &term) {
534537
unsigned int length;
535538

536539
if (!enif_get_list_length(env, term, &length)) {
537540
throw std::invalid_argument("decode failed, expected a list");
538541
}
539542

540-
std::vector<T> vector;
543+
std::vector<T, Alloc> vector;
541544
vector.reserve(length);
542545

543546
auto list = term;
544547

545548
ERL_NIF_TERM head, tail;
546549
while (enif_get_list_cell(env, list, &head, &tail)) {
547550
auto elem = fine::decode<T>(env, head);
548-
vector.push_back(elem);
551+
vector.emplace_back(std::move(elem));
549552
list = tail;
550553
}
551554

552555
return vector;
553556
}
554557
};
555558

556-
template <typename K, typename V> struct Decoder<std::map<K, V>> {
557-
static std::map<K, V> decode(ErlNifEnv *env, const ERL_NIF_TERM &term) {
558-
auto map = std::map<K, V>();
559+
template <typename K, typename V, typename Compare, typename Alloc>
560+
struct Decoder<std::map<K, V, Compare, Alloc>> {
561+
static std::map<K, V, Compare, Alloc> decode(ErlNifEnv *env,
562+
const ERL_NIF_TERM &term) {
563+
std::map<K, V, Compare, Alloc> map;
559564

560-
ERL_NIF_TERM key, value;
565+
ERL_NIF_TERM key_term, value_term;
561566
ErlNifMapIterator iter;
562567
if (!enif_map_iterator_create(env, term, &iter,
563568
ERL_NIF_MAP_ITERATOR_FIRST)) {
@@ -567,8 +572,12 @@ template <typename K, typename V> struct Decoder<std::map<K, V>> {
567572
// Define RAII cleanup for the iterator
568573
auto cleanup = IterCleanup{env, iter};
569574

570-
while (enif_map_iterator_get_pair(env, &iter, &key, &value)) {
571-
map[fine::decode<K>(env, key)] = fine::decode<V>(env, value);
575+
while (enif_map_iterator_get_pair(env, &iter, &key_term, &value_term)) {
576+
auto key = fine::decode<K>(env, key_term);
577+
auto value = fine::decode<V>(env, value_term);
578+
579+
map.insert_or_assign(std::move(key), std::move(value));
580+
572581
enif_map_iterator_next(env, &iter);
573582
}
574583

@@ -713,8 +722,11 @@ template <> struct Encoder<std::string_view> {
713722
}
714723
};
715724

716-
template <> struct Encoder<std::string> {
717-
static ERL_NIF_TERM encode(ErlNifEnv *env, const std::string &string) {
725+
template <typename Alloc>
726+
struct Encoder<std::basic_string<char, std::char_traits<char>, Alloc>> {
727+
static ERL_NIF_TERM
728+
encode(ErlNifEnv *env,
729+
const std::basic_string<char, std::char_traits<char>, Alloc> &string) {
718730
return fine::encode<std::string_view>(env, string);
719731
}
720732
};
@@ -783,8 +795,9 @@ template <typename... Args> struct Encoder<std::tuple<Args...>> {
783795
}
784796
};
785797

786-
template <typename T> struct Encoder<std::vector<T>> {
787-
static ERL_NIF_TERM encode(ErlNifEnv *env, const std::vector<T> &vector) {
798+
template <typename T, typename Alloc> struct Encoder<std::vector<T, Alloc>> {
799+
static ERL_NIF_TERM encode(ErlNifEnv *env,
800+
const std::vector<T, Alloc> &vector) {
788801
auto terms = std::vector<ERL_NIF_TERM>();
789802
terms.reserve(vector.size());
790803

@@ -797,8 +810,10 @@ template <typename T> struct Encoder<std::vector<T>> {
797810
}
798811
};
799812

800-
template <typename K, typename V> struct Encoder<std::map<K, V>> {
801-
static ERL_NIF_TERM encode(ErlNifEnv *env, const std::map<K, V> &map) {
813+
template <typename K, typename V, typename Compare, typename Alloc>
814+
struct Encoder<std::map<K, V, Compare, Alloc>> {
815+
static ERL_NIF_TERM encode(ErlNifEnv *env,
816+
const std::map<K, V, Compare, Alloc> &map) {
802817
auto keys = std::vector<ERL_NIF_TERM>();
803818
auto values = std::vector<ERL_NIF_TERM>();
804819

test/c_src/finest.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include <cstring>
22
#include <exception>
3+
#include <memory_resource>
34
#include <optional>
45
#include <stdexcept>
56
#include <thread>
@@ -113,6 +114,16 @@ FINE_NIF(codec_string_view, 0);
113114
std::string codec_string(ErlNifEnv *, std::string term) { return term; }
114115
FINE_NIF(codec_string, 0);
115116

117+
std::basic_string<char, std::char_traits<char>,
118+
std::pmr::polymorphic_allocator<char>>
119+
codec_string_alloc(ErlNifEnv *,
120+
std::basic_string<char, std::char_traits<char>,
121+
std::pmr::polymorphic_allocator<char>>
122+
term) {
123+
return term;
124+
}
125+
FINE_NIF(codec_string_alloc, 0);
126+
116127
fine::Atom codec_atom(ErlNifEnv *, fine::Atom term) { return term; }
117128
FINE_NIF(codec_atom, 0);
118129

@@ -145,11 +156,30 @@ std::vector<int64_t> codec_vector_int64(ErlNifEnv *,
145156
}
146157
FINE_NIF(codec_vector_int64, 0);
147158

159+
std::vector<int64_t, std::pmr::polymorphic_allocator<int64_t>>
160+
codec_vector_int64_alloc(
161+
ErlNifEnv *,
162+
std::vector<int64_t, std::pmr::polymorphic_allocator<int64_t>> term) {
163+
return term;
164+
}
165+
FINE_NIF(codec_vector_int64_alloc, 0);
166+
148167
std::map<fine::Atom, int64_t>
149168
codec_map_atom_int64(ErlNifEnv *, std::map<fine::Atom, int64_t> term) {
150169
return term;
151170
}
152171
FINE_NIF(codec_map_atom_int64, 0);
172+
std::map<fine::Atom, int64_t, std::less<fine::Atom>,
173+
std::pmr::polymorphic_allocator<std::pair<const fine::Atom, int64_t>>>
174+
codec_map_atom_int64_alloc(
175+
ErlNifEnv *,
176+
std::map<
177+
fine::Atom, int64_t, std::less<fine::Atom>,
178+
std::pmr::polymorphic_allocator<std::pair<const fine::Atom, int64_t>>>
179+
term) {
180+
return term;
181+
}
182+
FINE_NIF(codec_map_atom_int64_alloc, 0);
153183

154184
fine::ResourcePtr<TestResource>
155185
codec_resource(ErlNifEnv *, fine::ResourcePtr<TestResource> term) {

test/lib/finest/nif.ex

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,16 @@ defmodule Finest.NIF do
2323
def codec_binary(_term), do: err!()
2424
def codec_string_view(_term), do: err!()
2525
def codec_string(_term), do: err!()
26+
def codec_string_alloc(_term), do: err!()
2627
def codec_atom(_term), do: err!()
2728
def codec_nullopt(), do: err!()
2829
def codec_optional_int64(_term), do: err!()
2930
def codec_variant_int64_or_string(_term), do: err!()
3031
def codec_tuple_int64_and_string(_term), do: err!()
3132
def codec_vector_int64(_term), do: err!()
33+
def codec_vector_int64_alloc(_term), do: err!()
3234
def codec_map_atom_int64(_term), do: err!()
35+
def codec_map_atom_int64_alloc(_term), do: err!()
3336
def codec_resource(_term), do: err!()
3437
def codec_struct(_term), do: err!()
3538
def codec_struct_exception(_term), do: err!()

test/test/finest_test.exs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ defmodule FinestTest do
8282
assert NIF.codec_string(<<0, 1, 2>>) == <<0, 1, 2>>
8383
assert NIF.codec_string(<<>>) == <<>>
8484

85+
assert NIF.codec_string_alloc("hello world") == "hello world"
86+
assert NIF.codec_string_alloc(<<0, 1, 2>>) == <<0, 1, 2>>
87+
assert NIF.codec_string_alloc(<<>>) == <<>>
88+
8589
assert_raise ArgumentError, "decode failed, expected a binary", fn ->
8690
NIF.codec_string(1)
8791
end
@@ -144,6 +148,7 @@ defmodule FinestTest do
144148

145149
test "vector" do
146150
assert NIF.codec_vector_int64([1, 2, 3]) == [1, 2, 3]
151+
assert NIF.codec_vector_int64_alloc([1, 2, 3]) == [1, 2, 3]
147152

148153
assert_raise ArgumentError, "decode failed, expected a list", fn ->
149154
NIF.codec_vector_int64(10)
@@ -156,6 +161,7 @@ defmodule FinestTest do
156161

157162
test "map" do
158163
assert NIF.codec_map_atom_int64(%{hello: 1, world: 2}) == %{hello: 1, world: 2}
164+
assert NIF.codec_map_atom_int64_alloc(%{hello: 1, world: 2}) == %{hello: 1, world: 2}
159165

160166
assert_raise ArgumentError, "decode failed, expected a map", fn ->
161167
NIF.codec_map_atom_int64(10)

0 commit comments

Comments
 (0)