Intermediate usage

This section introduces encoders and decoders.

Encoders and decoders

Every codec is built on top of an encoder and a decoder.

They both model the codecs::input_source concept and thus allow lazy encoding/decoding.

To create them, use the codec traits’ make_encoder/make_decoder static member functions. Then, use the read member function to read sequences of elements.

#include <string>

#include <mgs/base64.hpp>

using namespace mgs;

int main() {
  std::string const decoded_string("decoded");
  // retrieves the exact encoded size
  auto const encoded_size = base64::encoded_size(decoded_string.size());

  std::string encoded_string(encoded_size, 0);
  auto encoder = base64::make_encoder(decoded_string);

  auto size_to_read = encoded_size;
  auto read_result = encoder.read(encoded_string.begin(), size_to_read);

  while (read_result.second != 0) {
    size_to_read -= read_result.second;
    read_result = encoder.read(read_result.first, size_to_read);
  }
}

In the above example, read will attempt to read size_to_read elements into an iterator.

However, implementation is allowed to read less than size_to_read, which means subsequent calls to read might be necessary to read all data.

For that matter, read returns a pair containing:

  • the iterator pointing after the last written element

  • the number of elements written, 0 being EOF

Warning

Encoders and decoders are stateful objects designed for one-time use, be careful to not reuse them!

Encoders as input ranges

mgs provides a way to wrap a codecs::input_source into a meta::input_range.

Though less efficient, this reduces the amount of code needed to read from a codecs::input_source:

#include <algorithm>
#include <iostream>
#include <utility>

#include <mgs/base64.hpp>
#include <mgs/codecs/basic_input_range.hpp>

using namespace mgs;

int main() {
  auto encoder = base64::make_encoder("decoded");
  auto encoding_range = codecs::make_input_range(std::move(encoder));

  // STL algorithms can be used with input ranges' iterators
  std::copy(encoding_range.begin(), encoding_range.end(),
            std::ostreambuf_iterator<char>(std::cout));
}

Tip

While more verbose, using read is usually faster than using iterators.

When to use encoders?

Adding support for user-defined types

Although mgs supports quite a few return types, you might use one in your code that is not supported by default.

Customizing the libary for that type requires using encoders/decoders along with the codecs::output_traits customization point.

As an example, here is the code needed for QLinkedList:

#include <mgs/codecs/output_traits_fwd.hpp>
#include <mgs/codecs/basic_input_range.hpp>
#include <mgs/base64.hpp>

#include <QLinkedList>

#include <algorithm>
#include <iterator>

namespace mgs {
namespace codecs {

template <typename CharT>
struct output_traits<QLinkedList<CharT>> {

  // In this case, InputSource is either an encoder or a decoder
  template <typename InputSource>
  static QLinkedList<CharT> create(InputSource source) {
    QLinkedList<CharT> list;
    // not the most efficient way
    auto range = make_input_range(std::move(source));
    std::copy(range.begin(), range.end(), std::back_inserter(list));
    return list;
  }
};

} // namespace codecs
} // namespace mgs

int main() {
  auto encoded_qlist = mgs::base64::encode<QLinkedList<char>>("Hello, World!");
}

Note

codecs::output_traits has a second template parameter, defaulted to void, which can be used to perform SFINAE checks.

Limiting memory usage

When dealing with huge volumes of data, memory consumption can become a problem.

Using the API shown in the basic section demonstrates it well:

#include <fstream>

#include <mgs/base64.hpp>

using namespace mgs;

int main() {
  // Could be a few TB...
  std::ifstream enormous_file("TODO.txt");
  std::istreambuf_iterator<char> begin(enormous_file);
  std::istreambuf_iterator<char> end;

  // oops, likely to throw
  std::vector<char> content(begin, end);
  // oops, even more likely to throw
  auto encoded = base64::encode(content);

  // very unlikely to be reached
  std::ofstream ofs("TODO.b64");
  ofs.write(encoded.c_str(), encoded.size());
}

In this case, std::bad_alloc will likely be thrown when constructing content or encoded.

Here is how to handle it properly:

#include <algorithm>
#include <fstream>

#include <mgs/base64.hpp>
#include <mgs/codecs/basic_input_range.hpp>

using namespace mgs;

int main() {
  std::ifstream enormous_file("TODO.txt");
  std::istreambuf_iterator<char> begin(enormous_file);
  std::istreambuf_iterator<char> end;

  auto encoder = base64::make_encoder(begin, end);
  auto encoding_range = codecs::make_input_range(std::move(encoder));

  std::ofstream ofs("TODO.b64");
  std::copy(encoding_range.begin(), encoding_range.end(),
            std::ostreambuf_iterator<char>(ofs));
}