3

I'm implementing a HTTP server and the API I follow define the raw response data as a std::vector<std::byte>>.

I store http responses headers as std::string in my code and at some point I have to write them to to raw response data before sending it back.

The thing is, I cannot find a clean way to write/append data from a std::string to my std::vector<std::byte>> (by clean way I mean not looping on the string and appending each char).

What is the best way to do that ?

Side question: What is the best way to read a string from a std::vector<std::byte>> ?

4
  • 2
    Insert at the end using iterators? Commented Feb 12, 2018 at 15:19
  • And to create a string, use iterators as well (std::string have a constructor taking an iterator pair)? Commented Feb 12, 2018 at 15:20
  • @Someprogrammerdude Not sure I understand what you mean. Something like this ? ` std::string rr = "http/1.1"; std::vector<std::byte>> raw; raw.insert(raw.begin(), rr.begin(), rr.end());` Commented Feb 12, 2018 at 15:28
  • Does not work with C++17 :) char do not fit in std::byte std::transform(headers.begin(), headers.end(), std::back_inserter(response), [](char c) { return std::byte(c); } ); where lambda is encode(). Commented Feb 12, 2018 at 15:30

4 Answers 4

5

Just use the ranged-insert overload (#4):

void extend(std::vector<std::byte>& v, std::string const& s) {
    auto bytes = reinterpret_cast<std::byte const*>(s.data());
    v.insert(v.end(), bytes, bytes + s.size());
}

You can read char as byte, it's a permitted alias.

Sign up to request clarification or add additional context in comments.

Comments

3

The char do cannot be converted to std::byte . Its defined as scoped enum:

enum class byte : unsigned char {} ;

cppreference.com std::byte

A numeric value n can be converted to a byte value using std::byte{n}, due to C++17 relaxed enum class initialization rules.

What you can do is use a helper function or lambda:

std::string headers;
std::vector<std::byte> response;

response.reserve(response.size() + headers.size());  // Optional
std::transform(headers.begin(), headers.end(), std::back_inserter(response),
    [](unsigned char c) { return std::byte{c}; }              // or `encode(c)`
);  

You can also resize the response and skip the back_inserter:

const auto response_size = response.size();
response.resize(response_size + headers.size());
std::transform(headers.begin(), headers.end(), std::next(response.begin(), response_size),
    [](unsigned char c) { return std::byte{c}; }
);  

Actually the whole will be optimized by the compiler to something similar to std::copy.

Or just replace std::byte with a char and use std::vector::insert() or std::copy().

Comments

1

First start with a gsl::span or similar.

template<class T>
struct span {
  T* b =0, *e = 0;
  T* begin() const { return b; }
  T* end() const { return e; }
  std::size_t size() const { return end()-begin(); }
  bool empty() const { return end()==begin(); }
  span( T* s, T* f ):b(s),e(f) {}
  span( T* s, std::size_t len ):span(s, s+len) {}

  template<class Uptr>
  using is_compatible = std::is_convertible< Uptr*, T* >;

  template<class R,
    std::enable_if_t<!std::is_same<std::decay_t<R>, span>{}, bool> = true,
    std::enable_if_t<is_compatible<decltype(std::declval<R&>().data())>{}, bool> = true
  >
  span( R&& r ):
    span(r.data(), r.size())
  {}
  template<class U, std::size_t N,
    std::enable_if_t<is_compatible<U*>{}, bool> = true
  >
  span( U(&arr)[N] ):span(arr, N) {}
};

now we have an abstraction for "possibly mutable view into contiguous Ts".

std::vector<std::byte> concat_string( std::vector<std::byte> lhs, span<char const> rhs ) {
  lhs.reserve(lhs.size()+rhs.size());
  lhs.insert( lhs.end(), (std::byte const*)rhs.begin(), (std::byte const*)rhs.end() );
  return rhs;
}

this assumes you don't want to embed the '\0'.

Comments

0

you'll want something like the following. I realize this still uses copy but there is only the one memory allocation which is the expensive part.

std::vector<std::byte> data;
std::string input;
...
data.reserve(data.size() + input.size());
std::copy(input.begin(), input.end(), std::back_inserter(data));

1 Comment

This does not compile :/ error: no match for ‘operator=’ (operand types are ‘std::back_insert_iterator<std::vector<std::byte> >’ and ‘const char’)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.