Skip to content

Commit

Permalink
Use iterators to encode data
Browse files Browse the repository at this point in the history
This is faster (at least for certain kinds of data) and should also make it
easier to support `std::byte`, since `std::basic_ostream`s aren't designed to
work with bytes (they aren't characters and so have no `std::char_traits`).
  • Loading branch information
jimporter committed Aug 9, 2024
1 parent ff3e305 commit 2904af7
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 46 deletions.
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
- `bencode::data` (and `bencode::basic_data`, etc) now support `operator []` and
`at` member functions to get list/dictionary elements
- Decoding functions now accept a pointer plus length as input
- Improve performance of `encode`; encoding is now up to 2x as fast, depending
on the data being encoded

### Breaking changes
- Require C++20
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,10 @@ auto str = bencode::encode(42);
// Encode and output to an std::ostream.
bencode::encode(std::cout, 42);
// Encode and output to an iterator.
std::vector<char> vec;
bencode::encode(std::back_inserter(vec), 42);
```

You can also construct more-complex data structures:
Expand Down
110 changes: 64 additions & 46 deletions include/bencode.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -690,130 +690,148 @@ namespace bencode {
#endif

namespace detail {
template<typename T>
concept output_iterator_ref = std::input_or_output_iterator<
std::remove_reference_t<T>
>;

template<output_iterator_ref Iter>
class list_encoder {
public:
inline list_encoder(std::ostream &os) : os(os) {
os.put('l');
inline list_encoder(Iter &iter) : iter(iter) {
*iter++ = 'l';
}

inline ~list_encoder() {
os.put('e');
*iter++ = 'e';
}

template<typename T>
inline list_encoder & add(T &&value);
private:
std::ostream &os;
Iter &iter;
};

template<output_iterator_ref Iter>
class dict_encoder {
public:
inline dict_encoder(std::ostream &os) : os(os) {
os.put('d');
inline dict_encoder(Iter &iter) : iter(iter) {
*iter++ = 'd';
}

inline ~dict_encoder() {
os.put('e');
*iter++ = 'e';
}

template<typename T>
inline dict_encoder & add(const string_view &key, T &&value);
private:
std::ostream &os;
Iter &iter;
};

template<typename T>
void write_integer(std::ostream &os, T value) {
template<detail::output_iterator_ref Iter, typename T>
void write_integer(Iter &iter, T value) {
// digits10 tells how many base-10 digits can fully fit in T, so we add 1
// for the digit that can only partially fit, plus one more for the
// negative sign.
char buf[std::numeric_limits<T>::digits10 + 2];
auto r = std::to_chars(buf, buf + sizeof(buf), value);
if(r.ec != std::errc())
throw std::system_error(std::make_error_code(r.ec));
os.write(buf, r.ptr - buf);
std::copy(buf, r.ptr, iter);
}
}

inline void encode(std::ostream &os, integer value) {
os.put('i');
detail::write_integer(os, value);
os.put('e');
template<detail::output_iterator_ref Iter>
inline void encode(Iter &&iter, integer value) {
*iter++ = 'i';
detail::write_integer(iter, value);
*iter++ = 'e';
}

inline void encode(std::ostream &os, const string_view &value) {
detail::write_integer(os, value.size());
os.put(':');
os.write(value.data(), value.size());
template<detail::output_iterator_ref Iter>
inline void encode(Iter &&iter, const string_view &value) {
detail::write_integer(iter, value.size());
*iter++ = ':';
std::copy(value.begin(), value.end(), iter);
}

template<typename T>
void encode(std::ostream &os, const std::vector<T> &value) {
detail::list_encoder e(os);
template<detail::output_iterator_ref Iter, typename T>
void encode(Iter &&iter, const std::vector<T> &value) {
detail::list_encoder e(iter);
for(auto &&i : value)
e.add(i);
}

template<typename T>
void encode(std::ostream &os, const std::map<string, T> &value) {
detail::dict_encoder e(os);
template<detail::output_iterator_ref Iter, typename T>
void encode(Iter &&iter, const std::map<string, T> &value) {
detail::dict_encoder e(iter);
for(auto &&i : value)
e.add(i.first, i.second);
}

template<typename T>
void encode(std::ostream &os, const std::map<string_view, T> &value) {
detail::dict_encoder e(os);
template<detail::output_iterator_ref Iter, typename T>
void encode(Iter &&iter, const std::map<string_view, T> &value) {
detail::dict_encoder e(iter);
for(auto &&i : value)
e.add(i.first, i.second);
}

template<typename K, typename V>
void encode(std::ostream &os, const map_proxy<K, V> &value) {
encode(os, *value);
template<detail::output_iterator_ref Iter, typename K, typename V>
void encode(Iter &&iter, const map_proxy<K, V> &value) {
encode(iter, *value);
}

namespace detail {
template<std::input_or_output_iterator Iter>
class encode_visitor {
public:
inline encode_visitor(std::ostream &os) : os(os) {}
inline encode_visitor(Iter &iter) : iter(iter) {}

template<typename T>
void operator ()(T &&operand) const {
encode(os, std::forward<T>(operand));
encode(iter, std::forward<T>(operand));
}
private:
std::ostream &os;
Iter &iter;
};
}

template<template<typename ...> typename Variant, typename I, typename S,
template<detail::output_iterator_ref Iter,
template<typename ...> typename Variant, typename I, typename S,
template<typename ...> typename L, template<typename ...> typename D>
void encode(std::ostream &os, const basic_data<Variant, I, S, L, D> &value) {
variant_traits<Variant>::visit(detail::encode_visitor(os), value);
void encode(Iter &&iter, const basic_data<Variant, I, S, L, D> &value) {
variant_traits<Variant>::visit(detail::encode_visitor(iter), value);
}

namespace detail {
template<detail::output_iterator_ref Iter>
template<typename T>
inline list_encoder & list_encoder::add(T &&value) {
encode(os, std::forward<T>(value));
inline list_encoder<Iter> & list_encoder<Iter>::add(T &&value) {
encode(iter, std::forward<T>(value));
return *this;
}

template<detail::output_iterator_ref Iter>
template<typename T>
inline dict_encoder &
dict_encoder::add(const string_view &key, T &&value) {
encode(os, key);
encode(os, std::forward<T>(value));
inline dict_encoder<Iter> &
dict_encoder<Iter>::add(const string_view &key, T &&value) {
encode(iter, key);
encode(iter, std::forward<T>(value));
return *this;
}
}

template<typename T>
std::string encode(T &&t) {
std::stringstream ss;
encode(ss, std::forward<T>(t));
return ss.str();
std::string result;
encode(std::back_inserter(result), std::forward<T>(t));
return result;
}

template<typename T>
void encode(std::ostream &os, T &&t) {
encode(std::ostreambuf_iterator(os), std::forward<T>(t));
}

}
Expand Down
61 changes: 61 additions & 0 deletions test/test_encode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,65 @@ suite<> test_encode("test encoder", [](auto &_) {
});
});

subsuite<std::stringstream>(_, "to std::ostream", [](auto &_) {
_.test("integer", [](std::stringstream &ss) {
bencode::encode(ss, 42);
expect(ss.str(), equal_to("i42e"));
});

_.test("string", [](std::stringstream &ss) {
bencode::encode(ss, "foo");
expect(ss.str(), equal_to("3:foo"));
});

_.test("list", [](std::stringstream &ss) {
bencode::encode(ss, bencode::list{1, "foo", 2});
expect(ss.str(), equal_to("l" "i1e" "3:foo" "i2e" "e"));
});

_.test("dict", [](std::stringstream &ss) {
bencode::encode(ss, bencode::dict{
{"one", 1},
{"two", "foo"},
{"three", 2}
});
expect(ss.str(), equal_to(
"d" "3:one" "i1e" "5:three" "i2e" "3:two" "3:foo" "e"
));
});

_.test("data", [](std::stringstream &ss) {
bencode::encode(ss, bencode::data{bencode::list{1, "foo", 2}});
expect(ss.str(), equal_to("l" "i1e" "3:foo" "i2e" "e"));
});
});

subsuite<std::vector<char>>(_, "to std::vector", [](auto &_) {
_.test("integer", [](std::vector<char> &v) {
bencode::encode(std::back_inserter(v), 42);
expect(v, array('i', '4', '2', 'e'));
});

_.test("string", [](std::vector<char> &v) {
bencode::encode(std::back_inserter(v), "foo");
expect(v, array('3', ':', 'f', 'o', 'o'));
});

_.test("list", [](std::vector<char> &v) {
bencode::encode(std::back_inserter(v), bencode::list{1, 2});
expect(v, array('l', 'i', '1', 'e', 'i', '2', 'e', 'e'));
});

_.test("dict", [](std::vector<char> &v) {
bencode::encode(std::back_inserter(v), bencode::dict{
{"one", 1},
{"two", 2},
});
expect(v, array(
'd', '3', ':', 'o', 'n', 'e', 'i', '1', 'e',
'3', ':', 't', 'w', 'o', 'i', '2', 'e', 'e'
));
});
});

});

0 comments on commit 2904af7

Please sign in to comment.