From a80f7e6396e49f35eeda1e757bb0109692c6902c Mon Sep 17 00:00:00 2001 From: Jim Porter 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 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 - concept sequence = iterable && !std::convertible_to; + concept stringish = iterable && requires(T &t) { + std::size(t); + requires std::same_as, char>; + }; template - concept mapping = sequence && requires { + concept mapping = iterable && requires { typename T::key_type; typename T::mapped_type; + requires stringish; }; template @@ -736,28 +740,42 @@ namespace bencode { } template - 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 + requires(!std::is_array_v) + 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 - 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 + inline void encode_to(Iter &&iter, const char (&value)[N]) { + // Don't write the null terminator. + encode_to(std::forward(iter), value, N - 1); } - template - void encode(Iter &&iter, const Seq &value) { + template + void encode_to(Iter &&iter, const Seq &value) { detail::list_encoder e(iter); for(auto &&i : value) e.add(i); } template - 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 void operator ()(T &&operand) const { - encode(iter, std::forward(operand)); + encode_to(iter, std::forward(operand)); } private: Iter &iter; @@ -781,7 +799,7 @@ namespace bencode { template typename Variant, typename I, typename S, template typename L, template typename D> - void encode(Iter &&iter, const basic_data &value) { + void encode_to(Iter &&iter, const basic_data &value) { variant_traits::visit(detail::encode_visitor(iter), value); } @@ -789,7 +807,7 @@ namespace bencode { template template inline list_encoder & list_encoder::add(T &&value) { - encode(iter, std::forward(value)); + encode_to(iter, std::forward(value)); return *this; } @@ -797,22 +815,22 @@ namespace bencode { template inline dict_encoder & dict_encoder::add(const string_view &key, T &&value) { - encode(iter, key); - encode(iter, std::forward(value)); + encode_to(iter, key); + encode_to(iter, std::forward(value)); return *this; } } - template - std::string encode(T &&t) { + template + std::string encode(T &&...t) { std::stringstream ss; - encode(std::ostreambuf_iterator(ss), std::forward(t)); + encode_to(std::ostreambuf_iterator(ss), std::forward(t)...); return ss.str(); } - template - void encode(std::ostream &os, T &&t) { - encode(std::ostreambuf_iterator(os), std::forward(t)); + template + void encode_to(std::ostream &os, T &&...t) { + encode_to(std::ostreambuf_iterator(os), std::forward(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(_, "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>(_, "to std::vector", [](auto &_) { _.test("integer", [](std::vector &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 &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 &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 &v) { - bencode::encode(std::back_inserter(v), bencode::dict{ + bencode::encode_to(std::back_inserter(v), bencode::dict{ {"one", 1}, {"two", 2}, });