Skip to content

Commit 1149a2a

Browse files
authored
Merge pull request #51 from jpetso/master
Finishing touches for v0.2
2 parents 4353619 + d8dfdc2 commit 1149a2a

File tree

9 files changed

+223
-49
lines changed

9 files changed

+223
-49
lines changed

.travis.yml

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,23 +32,21 @@ matrix:
3232
env: BUILD_TYPE=Release GXX=4.8
3333

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

4543
- os: linux
4644
compiler: clang
4745
addons:
4846
apt:
49-
sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-5.0' ]
50-
packages: [ 'clang-5.0', 'libstdc++-7-dev', 'libstdc++6' ] # C++17 support in libstd++
51-
env: BUILD_TYPE=Release CLANGXX=5.0 CXX_STD=17
47+
sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-6.0' ]
48+
packages: [ 'clang-6.0', 'libstdc++-7-dev', 'libstdc++6' ] # C++17 support in libstd++
49+
env: BUILD_TYPE=Release CLANGXX=6.0 CXX_STD=17
5250

5351
- os: linux
5452
compiler: clang
@@ -105,15 +103,15 @@ matrix:
105103
apt:
106104
sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-5.0' ]
107105
packages: [ 'clang-5.0' ]
108-
env: BUILD_TYPE=Debug CLANGXX=5.0
106+
env: BUILD_TYPE=Release CLANGXX=5.0
109107

110108
- os: linux
111109
compiler: clang
112110
addons:
113111
apt:
114112
sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-4.0' ]
115113
packages: [ 'clang-4.0' ]
116-
env: BUILD_TYPE=Release CLANGXX=4.0
114+
env: BUILD_TYPE=MinSizeRel CLANGXX=4.0
117115

118116
- os: linux
119117
compiler: clang

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ set(PUBLIC_HEADERS
6464
add_library(cppcodec OBJECT ${PUBLIC_HEADERS}) # unnecessary for building, but makes headers show up in IDEs
6565
set_target_properties(cppcodec PROPERTIES LINKER_LANGUAGE CXX)
6666
add_subdirectory(tool)
67+
add_subdirectory(example)
6768

6869
if (BUILD_TESTING)
6970
add_subdirectory(test)

README.md

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,32 +26,31 @@ Alternatively, you can install the headers and build extra tools/tests with CMak
2626

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

32-
cppcodec's approach is to implement encoding/decoding algorithms in different namespaces
33-
(e.g. `cppcodec::base64_rfc4648`) and in addition to the natural headers, also offer
34-
convenience headers to define a shorthand alias (e.g. `base64`) for one of the variants.
31+
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.
3532

3633
Here is an expected standard use of cppcodec:
3734

3835
```C++
39-
#include <cppcodec/base32_default_crockford.hpp>
40-
#include <cppcodec/base64_default_rfc4648.hpp>
36+
#include <cppcodec/base32_crockford.hpp>
37+
#include <cppcodec/base64_rfc4648.hpp>
4138
#include <iostream>
4239

4340
int main() {
44-
std::vector<uint8_t> decoded = base64::decode("YW55IGNhcm5hbCBwbGVhc3VyZQ==");
45-
std::cout << "decoded size (\"any carnal pleasure\"): " << decoded.size() << '\n';
46-
std::cout << base32::encode(decoded) << std::endl; // "C5Q7J833C5S6WRBC41R6RSB1EDTQ4S8"
47-
return 0;
41+
using base32 = cppcodec::base32_crockford;
42+
using base64 = cppcodec::base64_rfc4648;
43+
44+
std::vector<uint8_t> decoded = base64::decode("YW55IGNhcm5hbCBwbGVhc3VyZQ==");
45+
std::cout << "decoded size (\"any carnal pleasure\"): " << decoded.size() << '\n';
46+
std::cout << base32::encode(decoded) << std::endl; // "C5Q7J833C5S6WRBC41R6RSB1EDTQ4S8"
47+
return 0;
4848
}
4949
```
5050

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

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

5655
### base64
5756

@@ -116,6 +115,27 @@ communicated via phone.
116115

117116

118117

118+
# Philosophy and trade-offs
119+
120+
cppcodec aims to support a range of codecs using a shared template-based implementation.
121+
The focus is on a high-quality API that encourages correct use, includes error handling,
122+
and is easy to adopt into other codebases. As a header-only library, cppcodec can
123+
ship implementations of several codecs and variants while only compiling the ones
124+
that you actually use.
125+
126+
Good performance is a goal, but not the topmost priority. In theory, templates allows
127+
to write generic code that is optimized for each specialization individually; however,
128+
in practice compilers still struggle to produce code that's as simple as a
129+
hand-written specialized function. On release builds, depending on the C++ compiler,
130+
cppcodec runs in between (approx.) 100% and 300% of time compared to "regular" optimized
131+
base64 implementations. Both are beat by highly optimized implementations that use
132+
vector instructions (such as [this](https://github.com/aklomp/base64)) or buy better
133+
performance with larger pre-computed tables (such as Chrome's base64 implementation).
134+
Debug builds of cppcodec are slower by an order of magnitude due to the use of templates
135+
and abstractions; make sure you use release or minimum-size builds in production.
136+
137+
138+
119139
# API
120140

121141
All codecs expose the same API. In the below documentation, replace `<codec>` with a

TODO.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,6 @@ Here are a number of things I'd like to do still:
3434
return a temporary raw_result_buffer instead of being passed down as itself,
3535
for use cases where both std::vector and raw pointer calls are in use.
3636

37-
* Benchmark our performance against other libraries. We should be pretty fast
38-
since cppcodec is avoiding unnecessary copies or object construction, but
39-
we're also not doing any special vectorization or inline assembly. Plus it
40-
would be nice to know that the compiler optimizes the inline function calls
41-
well, instead of merely assuming it.
42-
4337
* More codec variants:
4438
* binary - useful for debugging
4539
* octal

appveyor.yml

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,34 +6,39 @@ branches:
66

77
clone_depth: 1
88

9+
# Build configurations, for MSBuild as well as ctest.
10+
configuration:
11+
- Release
12+
- MinSizeRel
13+
- Debug
14+
915
os:
1016
- Visual Studio 2015
1117
- Visual Studio 2017
1218

1319
# Win32 and x64 are CMake-compatible solution platform names.
1420
# This allows us to pass %PLATFORM% to cmake -A.
1521
platform:
16-
- Win32
1722
- x64
18-
19-
# Build configurations, for MSBuild as well as ctest.
20-
configuration:
21-
- Debug
22-
- Release
23-
- MinSizeRel
23+
- Win32
2424

2525
matrix:
2626
exclude:
27-
- os: Visual Studio 2017
27+
- os: Visual Studio 2015
2828
platform: x64
29-
configuration: Debug
29+
configuration: MinSizeRel
3030
- os: Visual Studio 2015
3131
platform: Win32
3232
configuration: Debug
3333
- os: Visual Studio 2017
34+
platform: Win32
3435
configuration: Release
35-
- platform: Win32
36-
configuration: Release
36+
- os: Visual Studio 2017
37+
platform: Win32
38+
configuration: MinSizeRel
39+
- os: Visual Studio 2017
40+
platform: x64
41+
configuration: Debug
3742

3843
install:
3944
- set SRC_DIR=%CD%

example/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# For cppcodec itself, don't prefer system headers over development ones.
2+
include_directories(BEFORE ${PROJECT_SOURCE_DIR})
3+
4+
add_executable(helloworld helloworld.cpp)
5+
6+
add_executable(type_support_wrapper type_support_wrapper.cpp)
Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,16 @@
2424
* For more information, please refer to <http://unlicense.org/>
2525
*/
2626

27-
#include <cppcodec/base32_default_crockford.hpp>
28-
#include <cppcodec/base64_default_rfc4648.hpp>
27+
#include <cppcodec/base32_crockford.hpp>
28+
#include <cppcodec/base64_rfc4648.hpp>
2929
#include <iostream>
3030

3131
int main() {
32-
std::vector<uint8_t> decoded = base64::decode("YW55IGNhcm5hbCBwbGVhc3VyZQ==");
33-
std::cout << "decoded size (\"any carnal pleasure\"): " << decoded.size() << '\n';
34-
std::cout << base32::encode(decoded) << std::endl; // "C5Q7J833C5S6WRBC41R6RSB1EDTQ4S8"
35-
return 0;
32+
using base32 = cppcodec::base32_crockford;
33+
using base64 = cppcodec::base64_rfc4648;
34+
35+
std::vector<uint8_t> decoded = base64::decode("YW55IGNhcm5hbCBwbGVhc3VyZQ==");
36+
std::cout << "decoded size (\"any carnal pleasure\"): " << decoded.size() << '\n';
37+
std::cout << base32::encode(decoded) << std::endl; // "C5Q7J833C5S6WRBC41R6RSB1EDTQ4S8"
38+
return 0;
3639
}

example/type_support_wrapper.cpp

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/**
2+
* This is free and unencumbered software released into the public domain.
3+
* Anyone is free to copy, modify, publish, use, compile, sell, or
4+
* distribute this software, either in source code form or as a compiled
5+
* binary, for any purpose, commercial or non-commercial, and by any
6+
* means.
7+
*
8+
* In jurisdictions that recognize copyright laws, the author or authors
9+
* of this software dedicate any and all copyright interest in the
10+
* software to the public domain. We make this dedication for the benefit
11+
* of the public at large and to the detriment of our heirs and
12+
* successors. We intend this dedication to be an overt act of
13+
* relinquishment in perpetuity of all present and future rights to this
14+
* software under copyright law.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19+
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20+
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21+
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22+
* OTHER DEALINGS IN THE SOFTWARE.
23+
*
24+
* For more information, please refer to <http://unlicense.org/>
25+
*/
26+
27+
#include <cppcodec/base64_rfc4648.hpp>
28+
#include <iostream>
29+
30+
31+
// This example shows how to wrap a type (here: std::string) in another
32+
// result type in order to modify the standard behavior for that type.
33+
// Use this when you're in a position to wrap the result variable.
34+
//
35+
// This is the more straightforward of two ways for modifying cppcodec's
36+
// behavior for a given type, the other one being able to modify the
37+
// default behavior but also being more complex/intricate.
38+
//
39+
// The overall approach is straightforward: Define a result type with
40+
// push_back(char) and size() methods, implement template specializations
41+
// for init() and finish() for the result type, and call encode()/decode()
42+
// with an object of this type as result parameter.
43+
44+
class string_append_wrapper
45+
{
46+
public:
47+
string_append_wrapper(std::string& backing)
48+
: m_backing(backing)
49+
, m_offset(0)
50+
, m_orig_size(0)
51+
{
52+
}
53+
54+
void init(size_t capacity)
55+
{
56+
m_orig_size = m_backing.size();
57+
m_offset = m_orig_size;
58+
m_backing.resize(m_orig_size + capacity);
59+
}
60+
void finish()
61+
{
62+
m_backing.resize(m_offset);
63+
}
64+
65+
// Methods required for satisfying default result type requirements:
66+
CPPCODEC_ALWAYS_INLINE void push_back(char c) { m_backing[m_offset++] = c; }
67+
CPPCODEC_ALWAYS_INLINE size_t size() const { return m_offset - m_orig_size; }
68+
69+
// Note that the above implementation of push_back() is not the fastest,
70+
// because operator[] for std::string (for C++11 and above) still includes
71+
// a check for whether the size of the string fits into its allocation-less
72+
// character array union.
73+
//
74+
// With C++17 and above, it's legitimate to get the character array as a
75+
// mutable (non-const) char pointer, so this check can be skipped.
76+
// This is implemented via template specialization in cppcodec's
77+
// default behavior for std::string, but omitted here for simplicity.
78+
// If you need that last bit of extra performance, see
79+
// direct_data_access_result_state in cppcodec/data/access.hpp
80+
// for an example of optimal C++17 string access.
81+
82+
private:
83+
std::string& m_backing;
84+
size_t m_offset;
85+
size_t m_orig_size;
86+
};
87+
88+
89+
// init() and finish() must be declared in the cppcodec::data namespace.
90+
namespace cppcodec {
91+
namespace data {
92+
93+
template <> inline void init<string_append_wrapper>(
94+
string_append_wrapper& result, empty_result_state&, size_t capacity)
95+
{
96+
// init() is called to prepare the output buffer. cppcodec will call it
97+
// with the maximum output size, null termination not included.
98+
//
99+
// Any thrown exception will not be caught by cppcodec itself,
100+
// the caller of the encode/decode function is responsible for handling it.
101+
//
102+
// empty_result_state can be ignored in this case because the wrapper type
103+
// can carry all required state internally.
104+
//
105+
// In order to maximize performance, init() should generally try to
106+
// allocate or guarantee the entire output buffer at once, so that
107+
// subsequent calls to push_back() don't result in extra checks (slower)
108+
// or even re-allocations.
109+
110+
result.init(capacity);
111+
}
112+
113+
// Between init() and finish(), cppcodec will call result.push_back(char)
114+
// repeatedly, once for each output character with no rewinding.
115+
// While init() can ask for greater capacity than the final output length,
116+
// cppcodec guarantees that push_back() will never be called too often.
117+
//
118+
// (If you know exactly how long your output is, you could theoretically
119+
// overcommit on capacity while allocating only the exact expected length
120+
// of the output buffer. This is of course dangerous, because you can
121+
// hardly ever know for sure and everyone's often wrong, so don't try it
122+
// unless you have a business-critical reason to reduce/avoid the allocation.)
123+
124+
template <> inline void finish<string_append_wrapper>(
125+
string_append_wrapper& result, empty_result_state&)
126+
{
127+
// finish() is called after encoding/decoding is done.
128+
// Its main purpose is to reduce the size of the result type
129+
// from capacity to the actual (often slightly smaller) output length.
130+
//
131+
// After finish(), cppcodec will assert that result.size() does indeed
132+
// equal the number of times that push_back() has been called.
133+
134+
result.finish();
135+
}
136+
137+
} // namespace data
138+
} // namespace cppcodec
139+
140+
141+
int main() {
142+
using base64 = cppcodec::base64_rfc4648;
143+
144+
std::string result = "Result: ";
145+
string_append_wrapper appender(result);
146+
base64::encode(appender, std::string("any carnal pleasure"));
147+
std::cout << result << std::endl; // "Result: YW55IGNhcm5hbCBwbGVhc3VyZQ=="
148+
return 0;
149+
}

tool/CMakeLists.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,3 @@ add_executable(base64dec base64dec.cpp)
99

1010
add_executable(hexenc hexenc.cpp)
1111
add_executable(hexdec hexdec.cpp)
12-
13-
add_executable(helloworld helloworld.cpp)

0 commit comments

Comments
 (0)