From a80f7e6396e49f35eeda1e757bb0109692c6902c Mon Sep 17 00:00:00 2001 From: Jim Porter <itsjimporter@gmail.com> Date: Fri, 30 Aug 2024 18:38:42 -0700 Subject: [PATCH] Improve flexibility of encoding strings This will also help when we move to supporting `std::byte`. --- CHANGES.md | 1 + README.md | 6 ++--- include/bencode.hpp | 58 +++++++++++++++++++++++++++++--------------- test/test_encode.cpp | 19 ++++++++------- 4 files changed, 52 insertions(+), 32 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 88474e5..fdd2d10 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,6 +13,7 @@ - Require C++20 - To decode only the next bencode object in a string or stream, you must now call `bencode::decode_some` +- To encode into an iterator or stream, you must now call `bencode::encode_to` ### Bug fixes - `bencode::decode` and friends now throw an exception if there's any data diff --git a/README.md b/README.md index 8600471..1389daf 100644 --- a/README.md +++ b/README.md @@ -182,17 +182,17 @@ Encoding data is also straightforward: auto str = bencode::encode(42); // Encode and output to an std::ostream. -bencode::encode(std::cout, 42); +bencode::encode_to(std::cout, 42); // Encode and output to an iterator. std::vector<char> vec; -bencode::encode(std::back_inserter(vec), 42); +bencode::encode_to(std::back_inserter(vec), 42); ``` You can also construct more-complex data structures: ```c++ -bencode::encode(std::cout, bencode::dict{ +bencode::encode_to(std::cout, bencode::dict{ {"one", 1}, {"two", bencode::list{1, "foo", 2}}, {"three", "3"} diff --git a/include/bencode.hpp b/include/bencode.hpp index e304bc5..20132fa 100644 --- a/include/bencode.hpp +++ b/include/bencode.hpp @@ -335,12 +335,16 @@ namespace bencode { }; template<typename T> - concept sequence = iterable<T> && !std::convertible_to<T, std::string_view>; + concept stringish = iterable<T> && requires(T &t) { + std::size(t); + requires std::same_as<std::iter_value_t<decltype(std::begin(t))>, char>; + }; template<typename T> - concept mapping = sequence<T> && requires { + concept mapping = iterable<T> && requires { typename T::key_type; typename T::mapped_type; + requires stringish<typename T::key_type>; }; template<std::integral Integer> @@ -736,28 +740,42 @@ namespace bencode { } template<detail::output_iterator_ref Iter> - inline void encode(Iter &&iter, integer value) { + inline void encode_to(Iter &&iter, integer value) { *iter++ = u8'i'; detail::write_integer(iter, value); *iter++ = u8'e'; } + template<detail::output_iterator_ref Iter, detail::stringish Str> + requires(!std::is_array_v<Str>) + inline void encode_to(Iter &&iter, const Str &value) { + detail::write_integer(iter, std::size(value)); + *iter++ = u8':'; + std::copy(std::begin(value), std::end(value), iter); + } + template<detail::output_iterator_ref Iter> - inline void encode(Iter &&iter, const string_view &value) { - detail::write_integer(iter, value.size()); + inline void encode_to(Iter &&iter, const char *value, std::size_t length) { + detail::write_integer(iter, length); *iter++ = u8':'; - std::copy(value.begin(), value.end(), iter); + std::copy(value, value + length, iter); + } + + template<detail::output_iterator_ref Iter, std::size_t N> + inline void encode_to(Iter &&iter, const char (&value)[N]) { + // Don't write the null terminator. + encode_to(std::forward<Iter>(iter), value, N - 1); } - template<detail::output_iterator_ref Iter, detail::sequence Seq> - void encode(Iter &&iter, const Seq &value) { + template<detail::output_iterator_ref Iter, detail::iterable Seq> + void encode_to(Iter &&iter, const Seq &value) { detail::list_encoder e(iter); for(auto &&i : value) e.add(i); } template<detail::output_iterator_ref Iter, detail::mapping Map> - void encode(Iter &&iter, const Map &value) { + void encode_to(Iter &&iter, const Map &value) { detail::dict_encoder e(iter); for(auto &&i : value) e.add(i.first, i.second); @@ -771,7 +789,7 @@ namespace bencode { template<typename T> void operator ()(T &&operand) const { - encode(iter, std::forward<T>(operand)); + encode_to(iter, std::forward<T>(operand)); } private: Iter &iter; @@ -781,7 +799,7 @@ namespace bencode { template<detail::output_iterator_ref Iter, template<typename ...> typename Variant, typename I, typename S, template<typename ...> typename L, template<typename ...> typename D> - void encode(Iter &&iter, const basic_data<Variant, I, S, L, D> &value) { + void encode_to(Iter &&iter, const basic_data<Variant, I, S, L, D> &value) { variant_traits<Variant>::visit(detail::encode_visitor(iter), value); } @@ -789,7 +807,7 @@ namespace bencode { template<detail::output_iterator_ref Iter> template<typename T> inline list_encoder<Iter> & list_encoder<Iter>::add(T &&value) { - encode(iter, std::forward<T>(value)); + encode_to(iter, std::forward<T>(value)); return *this; } @@ -797,22 +815,22 @@ namespace bencode { template<typename T> inline dict_encoder<Iter> & dict_encoder<Iter>::add(const string_view &key, T &&value) { - encode(iter, key); - encode(iter, std::forward<T>(value)); + encode_to(iter, key); + encode_to(iter, std::forward<T>(value)); return *this; } } - template<typename T> - std::string encode(T &&t) { + template<typename ...T> + std::string encode(T &&...t) { std::stringstream ss; - encode(std::ostreambuf_iterator(ss), std::forward<T>(t)); + encode_to(std::ostreambuf_iterator(ss), std::forward<T>(t)...); return ss.str(); } - template<typename T> - void encode(std::ostream &os, T &&t) { - encode(std::ostreambuf_iterator(os), std::forward<T>(t)); + template<typename ...T> + void encode_to(std::ostream &os, T &&...t) { + encode_to(std::ostreambuf_iterator(os), std::forward<T>(t)...); } } diff --git a/test/test_encode.cpp b/test/test_encode.cpp index 40a31e6..57c4989 100644 --- a/test/test_encode.cpp +++ b/test/test_encode.cpp @@ -12,6 +12,7 @@ suite<> test_encode("test encoder", [](auto &_) { _.test("string", []() { expect(bencode::encode("foo"), equal_to("3:foo")); + expect(bencode::encode((char*)"foo", 3), equal_to("3:foo")); expect(bencode::encode(std::string("foo")), equal_to("3:foo")); expect(bencode::encode(bencode::string("foo")), equal_to("3:foo")); }); @@ -131,22 +132,22 @@ suite<> test_encode("test encoder", [](auto &_) { subsuite<std::stringstream>(_, "to std::ostream", [](auto &_) { _.test("integer", [](std::stringstream &ss) { - bencode::encode(ss, 42); + bencode::encode_to(ss, 42); expect(ss.str(), equal_to("i42e")); }); _.test("string", [](std::stringstream &ss) { - bencode::encode(ss, "foo"); + bencode::encode_to(ss, "foo"); expect(ss.str(), equal_to("3:foo")); }); _.test("list", [](std::stringstream &ss) { - bencode::encode(ss, bencode::list{1, "foo", 2}); + bencode::encode_to(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{ + bencode::encode_to(ss, bencode::dict{ {"one", 1}, {"two", "foo"}, {"three", 2} @@ -157,29 +158,29 @@ suite<> test_encode("test encoder", [](auto &_) { }); _.test("data", [](std::stringstream &ss) { - bencode::encode(ss, bencode::data{bencode::list{1, "foo", 2}}); + bencode::encode_to(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); + bencode::encode_to(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"); + bencode::encode_to(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}); + bencode::encode_to(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{ + bencode::encode_to(std::back_inserter(v), bencode::dict{ {"one", 1}, {"two", 2}, });