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},
       });