Skip to content

Commit

Permalink
Merge pull request tplgy#51 from jpetso/master
Browse files Browse the repository at this point in the history
Finishing touches for v0.2
  • Loading branch information
jpetso authored Jul 19, 2018
2 parents 4353619 + d8dfdc2 commit 1149a2a
Show file tree
Hide file tree
Showing 9 changed files with 223 additions and 49 deletions.
18 changes: 8 additions & 10 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,23 +32,21 @@ matrix:
env: BUILD_TYPE=Release GXX=4.8

# Newest/oldest clang
# At the time of writing, clang-6.0 doesn't yet apt-get install properly
# on Travis. Use 5.0 until the issue is fixed or a workaround is found.
- os: linux
compiler: clang
addons:
apt:
sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-5.0' ]
packages: [ 'clang-5.0', 'libstdc++-7-dev', 'libstdc++6' ] # C++17 support in libstd++
env: BUILD_TYPE=MinSizeRel CLANGXX=5.0 CXX_STD=17
sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-6.0' ]
packages: [ 'clang-6.0', 'libstdc++-7-dev', 'libstdc++6' ] # C++17 support in libstd++
env: BUILD_TYPE=MinSizeRel CLANGXX=6.0 CXX_STD=17

- os: linux
compiler: clang
addons:
apt:
sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-5.0' ]
packages: [ 'clang-5.0', 'libstdc++-7-dev', 'libstdc++6' ] # C++17 support in libstd++
env: BUILD_TYPE=Release CLANGXX=5.0 CXX_STD=17
sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-6.0' ]
packages: [ 'clang-6.0', 'libstdc++-7-dev', 'libstdc++6' ] # C++17 support in libstd++
env: BUILD_TYPE=Release CLANGXX=6.0 CXX_STD=17

- os: linux
compiler: clang
Expand Down Expand Up @@ -105,15 +103,15 @@ matrix:
apt:
sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-5.0' ]
packages: [ 'clang-5.0' ]
env: BUILD_TYPE=Debug CLANGXX=5.0
env: BUILD_TYPE=Release CLANGXX=5.0

- os: linux
compiler: clang
addons:
apt:
sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-4.0' ]
packages: [ 'clang-4.0' ]
env: BUILD_TYPE=Release CLANGXX=4.0
env: BUILD_TYPE=MinSizeRel CLANGXX=4.0

- os: linux
compiler: clang
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ set(PUBLIC_HEADERS
add_library(cppcodec OBJECT ${PUBLIC_HEADERS}) # unnecessary for building, but makes headers show up in IDEs
set_target_properties(cppcodec PROPERTIES LINKER_LANGUAGE CXX)
add_subdirectory(tool)
add_subdirectory(example)

if (BUILD_TESTING)
add_subdirectory(test)
Expand Down
48 changes: 34 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,32 +26,31 @@ Alternatively, you can install the headers and build extra tools/tests with CMak

A number of codec variants exist for base64 and base32, defining different alphabets
or specifying the use of padding and line breaks in different ways. cppcodec is designed
to let you make a conscious choice about which one you're using, but assumes you will
mostly stick to a single one.
to let you make a conscious choice about which one you're using, see below for a list of variants.

cppcodec's approach is to implement encoding/decoding algorithms in different namespaces
(e.g. `cppcodec::base64_rfc4648`) and in addition to the natural headers, also offer
convenience headers to define a shorthand alias (e.g. `base64`) for one of the variants.
cppcodec's approach is to implement encoding/decoding algorithms in different classes for namespacing (e.g. `cppcodec::base64_rfc4648`), with classes and their associated header files named verbatim after the codec variants.

Here is an expected standard use of cppcodec:

```C++
#include <cppcodec/base32_default_crockford.hpp>
#include <cppcodec/base64_default_rfc4648.hpp>
#include <cppcodec/base32_crockford.hpp>
#include <cppcodec/base64_rfc4648.hpp>
#include <iostream>

int main() {
std::vector<uint8_t> decoded = base64::decode("YW55IGNhcm5hbCBwbGVhc3VyZQ==");
std::cout << "decoded size (\"any carnal pleasure\"): " << decoded.size() << '\n';
std::cout << base32::encode(decoded) << std::endl; // "C5Q7J833C5S6WRBC41R6RSB1EDTQ4S8"
return 0;
using base32 = cppcodec::base32_crockford;
using base64 = cppcodec::base64_rfc4648;

std::vector<uint8_t> decoded = base64::decode("YW55IGNhcm5hbCBwbGVhc3VyZQ==");
std::cout << "decoded size (\"any carnal pleasure\"): " << decoded.size() << '\n';
std::cout << base32::encode(decoded) << std::endl; // "C5Q7J833C5S6WRBC41R6RSB1EDTQ4S8"
return 0;
}
```

If possible, avoid including "default" headers in other header files.
(The prior example included "baseXX_default_*.h" includes, these are not recommended anymore and may eventually get deprecated.)

Non-aliasing headers omit the "default" part, e.g. `<cppcodec/base64_rfc4648.hpp>`
or `<cppcodec/hex_lower.hpp>`. Currently supported variants are:
Currently supported codec variants are:

### base64

Expand Down Expand Up @@ -116,6 +115,27 @@ communicated via phone.



# Philosophy and trade-offs

cppcodec aims to support a range of codecs using a shared template-based implementation.
The focus is on a high-quality API that encourages correct use, includes error handling,
and is easy to adopt into other codebases. As a header-only library, cppcodec can
ship implementations of several codecs and variants while only compiling the ones
that you actually use.

Good performance is a goal, but not the topmost priority. In theory, templates allows
to write generic code that is optimized for each specialization individually; however,
in practice compilers still struggle to produce code that's as simple as a
hand-written specialized function. On release builds, depending on the C++ compiler,
cppcodec runs in between (approx.) 100% and 300% of time compared to "regular" optimized
base64 implementations. Both are beat by highly optimized implementations that use
vector instructions (such as [this](https://github.com/aklomp/base64)) or buy better
performance with larger pre-computed tables (such as Chrome's base64 implementation).
Debug builds of cppcodec are slower by an order of magnitude due to the use of templates
and abstractions; make sure you use release or minimum-size builds in production.



# API

All codecs expose the same API. In the below documentation, replace `<codec>` with a
Expand Down
6 changes: 0 additions & 6 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,6 @@ Here are a number of things I'd like to do still:
return a temporary raw_result_buffer instead of being passed down as itself,
for use cases where both std::vector and raw pointer calls are in use.

* Benchmark our performance against other libraries. We should be pretty fast
since cppcodec is avoiding unnecessary copies or object construction, but
we're also not doing any special vectorization or inline assembly. Plus it
would be nice to know that the compiler optimizes the inline function calls
well, instead of merely assuming it.

* More codec variants:
* binary - useful for debugging
* octal
Expand Down
27 changes: 16 additions & 11 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,39 @@ branches:

clone_depth: 1

# Build configurations, for MSBuild as well as ctest.
configuration:
- Release
- MinSizeRel
- Debug

os:
- Visual Studio 2015
- Visual Studio 2017

# Win32 and x64 are CMake-compatible solution platform names.
# This allows us to pass %PLATFORM% to cmake -A.
platform:
- Win32
- x64

# Build configurations, for MSBuild as well as ctest.
configuration:
- Debug
- Release
- MinSizeRel
- Win32

matrix:
exclude:
- os: Visual Studio 2017
- os: Visual Studio 2015
platform: x64
configuration: Debug
configuration: MinSizeRel
- os: Visual Studio 2015
platform: Win32
configuration: Debug
- os: Visual Studio 2017
platform: Win32
configuration: Release
- platform: Win32
configuration: Release
- os: Visual Studio 2017
platform: Win32
configuration: MinSizeRel
- os: Visual Studio 2017
platform: x64
configuration: Debug

install:
- set SRC_DIR=%CD%
Expand Down
6 changes: 6 additions & 0 deletions example/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# For cppcodec itself, don't prefer system headers over development ones.
include_directories(BEFORE ${PROJECT_SOURCE_DIR})

add_executable(helloworld helloworld.cpp)

add_executable(type_support_wrapper type_support_wrapper.cpp)
15 changes: 9 additions & 6 deletions tool/helloworld.cpp → example/helloworld.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,16 @@
* For more information, please refer to <http://unlicense.org/>
*/

#include <cppcodec/base32_default_crockford.hpp>
#include <cppcodec/base64_default_rfc4648.hpp>
#include <cppcodec/base32_crockford.hpp>
#include <cppcodec/base64_rfc4648.hpp>
#include <iostream>

int main() {
std::vector<uint8_t> decoded = base64::decode("YW55IGNhcm5hbCBwbGVhc3VyZQ==");
std::cout << "decoded size (\"any carnal pleasure\"): " << decoded.size() << '\n';
std::cout << base32::encode(decoded) << std::endl; // "C5Q7J833C5S6WRBC41R6RSB1EDTQ4S8"
return 0;
using base32 = cppcodec::base32_crockford;
using base64 = cppcodec::base64_rfc4648;

std::vector<uint8_t> decoded = base64::decode("YW55IGNhcm5hbCBwbGVhc3VyZQ==");
std::cout << "decoded size (\"any carnal pleasure\"): " << decoded.size() << '\n';
std::cout << base32::encode(decoded) << std::endl; // "C5Q7J833C5S6WRBC41R6RSB1EDTQ4S8"
return 0;
}
149 changes: 149 additions & 0 deletions example/type_support_wrapper.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/**
* This is free and unencumbered software released into the public domain.
* Anyone is free to copy, modify, publish, use, compile, sell, or
* distribute this software, either in source code form or as a compiled
* binary, for any purpose, commercial or non-commercial, and by any
* means.
*
* In jurisdictions that recognize copyright laws, the author or authors
* of this software dedicate any and all copyright interest in the
* software to the public domain. We make this dedication for the benefit
* of the public at large and to the detriment of our heirs and
* successors. We intend this dedication to be an overt act of
* relinquishment in perpetuity of all present and future rights to this
* software under copyright law.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* For more information, please refer to <http://unlicense.org/>
*/

#include <cppcodec/base64_rfc4648.hpp>
#include <iostream>


// This example shows how to wrap a type (here: std::string) in another
// result type in order to modify the standard behavior for that type.
// Use this when you're in a position to wrap the result variable.
//
// This is the more straightforward of two ways for modifying cppcodec's
// behavior for a given type, the other one being able to modify the
// default behavior but also being more complex/intricate.
//
// The overall approach is straightforward: Define a result type with
// push_back(char) and size() methods, implement template specializations
// for init() and finish() for the result type, and call encode()/decode()
// with an object of this type as result parameter.

class string_append_wrapper
{
public:
string_append_wrapper(std::string& backing)
: m_backing(backing)
, m_offset(0)
, m_orig_size(0)
{
}

void init(size_t capacity)
{
m_orig_size = m_backing.size();
m_offset = m_orig_size;
m_backing.resize(m_orig_size + capacity);
}
void finish()
{
m_backing.resize(m_offset);
}

// Methods required for satisfying default result type requirements:
CPPCODEC_ALWAYS_INLINE void push_back(char c) { m_backing[m_offset++] = c; }
CPPCODEC_ALWAYS_INLINE size_t size() const { return m_offset - m_orig_size; }

// Note that the above implementation of push_back() is not the fastest,
// because operator[] for std::string (for C++11 and above) still includes
// a check for whether the size of the string fits into its allocation-less
// character array union.
//
// With C++17 and above, it's legitimate to get the character array as a
// mutable (non-const) char pointer, so this check can be skipped.
// This is implemented via template specialization in cppcodec's
// default behavior for std::string, but omitted here for simplicity.
// If you need that last bit of extra performance, see
// direct_data_access_result_state in cppcodec/data/access.hpp
// for an example of optimal C++17 string access.

private:
std::string& m_backing;
size_t m_offset;
size_t m_orig_size;
};


// init() and finish() must be declared in the cppcodec::data namespace.
namespace cppcodec {
namespace data {

template <> inline void init<string_append_wrapper>(
string_append_wrapper& result, empty_result_state&, size_t capacity)
{
// init() is called to prepare the output buffer. cppcodec will call it
// with the maximum output size, null termination not included.
//
// Any thrown exception will not be caught by cppcodec itself,
// the caller of the encode/decode function is responsible for handling it.
//
// empty_result_state can be ignored in this case because the wrapper type
// can carry all required state internally.
//
// In order to maximize performance, init() should generally try to
// allocate or guarantee the entire output buffer at once, so that
// subsequent calls to push_back() don't result in extra checks (slower)
// or even re-allocations.

result.init(capacity);
}

// Between init() and finish(), cppcodec will call result.push_back(char)
// repeatedly, once for each output character with no rewinding.
// While init() can ask for greater capacity than the final output length,
// cppcodec guarantees that push_back() will never be called too often.
//
// (If you know exactly how long your output is, you could theoretically
// overcommit on capacity while allocating only the exact expected length
// of the output buffer. This is of course dangerous, because you can
// hardly ever know for sure and everyone's often wrong, so don't try it
// unless you have a business-critical reason to reduce/avoid the allocation.)

template <> inline void finish<string_append_wrapper>(
string_append_wrapper& result, empty_result_state&)
{
// finish() is called after encoding/decoding is done.
// Its main purpose is to reduce the size of the result type
// from capacity to the actual (often slightly smaller) output length.
//
// After finish(), cppcodec will assert that result.size() does indeed
// equal the number of times that push_back() has been called.

result.finish();
}

} // namespace data
} // namespace cppcodec


int main() {
using base64 = cppcodec::base64_rfc4648;

std::string result = "Result: ";
string_append_wrapper appender(result);
base64::encode(appender, std::string("any carnal pleasure"));
std::cout << result << std::endl; // "Result: YW55IGNhcm5hbCBwbGVhc3VyZQ=="
return 0;
}
2 changes: 0 additions & 2 deletions tool/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,3 @@ add_executable(base64dec base64dec.cpp)

add_executable(hexenc hexenc.cpp)
add_executable(hexdec hexdec.cpp)

add_executable(helloworld helloworld.cpp)

0 comments on commit 1149a2a

Please sign in to comment.