├── .clang-format
├── .clang-tidy
├── .github
└── workflows
│ └── test.yml
├── .gitignore
├── CHANGELOG.md
├── CMakeLists.txt
├── CONTRIBUTING.md
├── LICENSE.md
├── Makefile
├── README.md
├── bench
└── run.cpp
├── cmake
└── mason.cmake
├── codecov.yml
├── include
├── hello_world.hpp
└── hello_world
│ ├── exclaim.hpp
│ ├── expensive.hpp
│ └── version.hpp
├── scripts
├── clang-tidy.sh
├── coverage.sh
├── format.sh
├── liftoff.sh
└── setup.sh
└── test
└── test.cpp
/.clang-format:
--------------------------------------------------------------------------------
1 | ---
2 | # Mapbox.Variant C/C+ style
3 | Language: Cpp
4 | AccessModifierOffset: -2
5 | AlignAfterOpenBracket: Align
6 | AlignConsecutiveAssignments: false
7 | AlignConsecutiveDeclarations: false
8 | AlignEscapedNewlinesLeft: false
9 | AlignOperands: true
10 | AlignTrailingComments: true
11 | AllowAllParametersOfDeclarationOnNextLine: true
12 | AllowShortBlocksOnASingleLine: false
13 | AllowShortCaseLabelsOnASingleLine: false
14 | AllowShortFunctionsOnASingleLine: All
15 | AllowShortIfStatementsOnASingleLine: true
16 | AllowShortLoopsOnASingleLine: true
17 | AlwaysBreakAfterDefinitionReturnType: None
18 | AlwaysBreakAfterReturnType: None
19 | AlwaysBreakBeforeMultilineStrings: false
20 | AlwaysBreakTemplateDeclarations: true
21 | BinPackArguments: true
22 | BinPackParameters: true
23 | BraceWrapping:
24 | AfterClass: true
25 | AfterControlStatement: true
26 | AfterEnum: true
27 | AfterFunction: true
28 | AfterNamespace: false
29 | AfterObjCDeclaration: true
30 | AfterStruct: true
31 | AfterUnion: true
32 | BeforeCatch: true
33 | BeforeElse: true
34 | IndentBraces: false
35 | BreakBeforeBinaryOperators: None
36 | BreakBeforeBraces: Custom
37 | BreakBeforeTernaryOperators: true
38 | BreakConstructorInitializersBeforeComma: false
39 | ColumnLimit: 0
40 | CommentPragmas: '^ IWYU pragma:'
41 | ConstructorInitializerAllOnOneLineOrOnePerLine: true
42 | ConstructorInitializerIndentWidth: 4
43 | ContinuationIndentWidth: 4
44 | Cpp11BracedListStyle: true
45 | DerivePointerAlignment: false
46 | DisableFormat: false
47 | ExperimentalAutoDetectBinPacking: false
48 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
49 | IncludeCategories:
50 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/'
51 | Priority: 2
52 | - Regex: '^(<|"(gtest|isl|json)/)'
53 | Priority: 3
54 | - Regex: '.*'
55 | Priority: 1
56 | IndentCaseLabels: false
57 | IndentWidth: 4
58 | IndentWrappedFunctionNames: false
59 | KeepEmptyLinesAtTheStartOfBlocks: true
60 | MacroBlockBegin: ''
61 | MacroBlockEnd: ''
62 | MaxEmptyLinesToKeep: 1
63 | NamespaceIndentation: None
64 | ObjCBlockIndentWidth: 2
65 | ObjCSpaceAfterProperty: false
66 | ObjCSpaceBeforeProtocolList: true
67 | PenaltyBreakBeforeFirstCallParameter: 19
68 | PenaltyBreakComment: 300
69 | PenaltyBreakFirstLessLess: 120
70 | PenaltyBreakString: 1000
71 | PenaltyExcessCharacter: 1000000
72 | PenaltyReturnTypeOnItsOwnLine: 60
73 | PointerAlignment: Left
74 | ReflowComments: true
75 | SortIncludes: false
76 | SpaceAfterCStyleCast: false
77 | SpaceBeforeAssignmentOperators: true
78 | SpaceBeforeParens: ControlStatements
79 | SpaceInEmptyParentheses: false
80 | SpacesBeforeTrailingComments: 1
81 | SpacesInAngles: false
82 | SpacesInContainerLiterals: true
83 | SpacesInCStyleCastParentheses: false
84 | SpacesInParentheses: false
85 | SpacesInSquareBrackets: false
86 | Standard: Cpp11
87 | TabWidth: 4
88 | UseTab: Never
89 |
--------------------------------------------------------------------------------
/.clang-tidy:
--------------------------------------------------------------------------------
1 | ---
2 | # clang-analyzer-core.uninitialized.UndefReturn can be removed once LLVM is upgrade to fix
3 | # https://bugs.llvm.org/show_bug.cgi?id=39201
4 | Checks: '*,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,-llvmlibc*,-cppcoreguidelines-macro-usage,-fuchsia*,-clang-analyzer-core.uninitialized.UndefReturn'
5 | WarningsAsErrors: '*'
6 | HeaderFilterRegex: '\/include\/'
7 | AnalyzeTemporaryDtors: false
8 | CheckOptions:
9 | - key: google-readability-braces-around-statements.ShortStatementLines
10 | value: '1'
11 | - key: google-readability-function-size.StatementThreshold
12 | value: '800'
13 | - key: google-readability-namespace-comments.ShortNamespaceLines
14 | value: '10'
15 | - key: google-readability-namespace-comments.SpacesBeforeComments
16 | value: '2'
17 | - key: modernize-loop-convert.MaxCopySize
18 | value: '16'
19 | - key: modernize-loop-convert.MinConfidence
20 | value: reasonable
21 | - key: modernize-loop-convert.NamingStyle
22 | value: CamelCase
23 | - key: modernize-pass-by-value.IncludeStyle
24 | value: llvm
25 | - key: modernize-replace-auto-ptr.IncludeStyle
26 | value: llvm
27 | - key: modernize-use-nullptr.NullMacros
28 | value: 'NULL'
29 | ...
30 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: mapbox/hpp-skel
2 | on:
3 | push:
4 | branches:
5 | - master
6 | pull_request:
7 | branches:
8 | - master
9 |
10 | jobs:
11 | test:
12 | runs-on: ubuntu-22.04
13 | permissions:
14 | id-token: write
15 | contents: read
16 |
17 | strategy:
18 | fail-fast: false
19 | matrix:
20 | cxx: ['g++-4.9', 'g++-5', 'clang++']
21 | cmake-version: ['3.15.2']
22 |
23 | steps:
24 | - uses: actions/checkout@v4
25 | - name: Install packages
26 | run: |
27 | sudo add-apt-repository ppa:ubuntu-toolchain-r/test
28 | sudo apt update
29 | sudo apt-get install -y libstdc++6 libstdc++-5-dev g++-4.9 g++-5
30 |
31 | - name: Install and setup
32 | run: |
33 | ./scripts/setup.sh --config local.env
34 | source local.env
35 | mason install cmake ${{ matrix.cmake-version }}
36 | mason link cmake ${{ matrix.cmake-version }}
37 | which cmake
38 |
39 | - name: Run tests
40 | run: |
41 | make release
42 | make test
43 | make clean
44 | make debug
45 | make test
46 | make clean
47 | env:
48 | CXX: ${{ matrix.cxx }}
49 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | mason_packages
3 | cmake-build
4 | *.profdata
5 | *.profraw
6 | .toolchain
7 | .mason
8 | local.env
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 11/17/2017
2 |
3 | * Update contributing and changelog
4 |
5 | # 11/16/2017
6 |
7 | * Liftoff [docs and fix](https://github.com/mapbox/hpp-skel/pull/57)
8 | * ]iwyu fix](https://github.com/mapbox/hpp-skel/pull/59)
9 |
10 | # 10/30/2017
11 |
12 | * [Add docs](https://github.com/mapbox/hpp-skel/pull/56/files) for how to set sanitizer flags
13 |
14 | # 10/23/2017
15 |
16 | * Do not [sort includes with clang format](https://github.com/mapbox/hpp-skel/pull/55)
17 |
18 | # 10/20/2017
19 |
20 | * Add [CC0 license](https://github.com/mapbox/hpp-skel/pull/54)
21 |
22 | # 10/17/2017
23 |
24 | * Add better [default warnings](https://github.com/mapbox/hpp-skel/pull/52) and update compiler flags per https://github.com/mapbox/cpp/issues/37#issuecomment-336200744
25 |
26 | # 10/12/2017
27 |
28 | * Add compiler flags/warnings per https://github.com/mapbox/cpp/issues/37
29 |
30 | # 09/12/2017
31 |
32 | * Add multithread benchmarking:
33 | * inline headers
34 | * collapse both benchmarks into one run.cpp
35 | * call binary directly from Makefile
36 | * [add ignores to codecov](https://github.com/mapbox/hpp-skel/pull/49)
37 | * [clang format fixes](https://github.com/mapbox/hpp-skel/pull/50)
38 | * Add clant-tidy NOLINT
39 | * anti-optimization and clobber methods
40 |
41 | # 08/25/2017
42 |
43 | * Add clang-format
44 | * Transition tests to use catch
45 | * [Port to cmake](https://github.com/mapbox/hpp-skel/pull/24)
46 | * [Add setup.sh and local coverage reporting](https://github.com/mapbox/hpp-skel/pull/27)
47 | * Add [first iteration](https://github.com/mapbox/hpp-skel/pull/29) of "liftoff" script
48 | * [Glob tests in cmake](https://github.com/mapbox/hpp-skel/pull/30) for added flexibility
49 | * [Disable OSX testing](https://github.com/mapbox/hpp-skel/pull/35) for now
50 |
51 | # 08/23/2017
52 |
53 | * move include directory up a level, now just /include/hello_world.cpp
54 | * update test directory structure to just use test.cpp instead of splitting into unit tests
55 | * default to Debug build, standardize build location to /build/Release and /build/Debug [#18](https://github.com/mapbox/hpp-skel/issues/18)
56 | * `make` command builds by default, tests are run with `make test`
57 | * add a version file [#19](https://github.com/mapbox/hpp-skel/issues/19)
58 |
59 | ## 08/25/2016
60 |
61 | * nest hello_world within include directory named after library
62 |
63 | ## 08/18/2016
64 |
65 | * add debug/release builds + more aggressive warnings
66 | * default to Release
67 | * Create test alias
68 | * c++11 as default
69 | * add `include_dirs.js` for NPM publishing and requiring
70 |
71 | ## 07/20/2016
72 |
73 | * the hpp-skel is born :baby:
74 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.8)
2 | project(hpp_skel LANGUAGES CXX)
3 | set(CMAKE_CXX_STANDARD 14)
4 | set(CMAKE_CXX_STANDARD_REQUIRED ON)
5 | set(CMAKE_CXX_EXTENSIONS OFF)
6 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
7 |
8 | include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/mason.cmake)
9 |
10 | option(WERROR "Add -Werror flag to build (turns warnings into errors)" ON)
11 |
12 | # configure optimization
13 | if (CMAKE_BUILD_TYPE STREQUAL "Debug")
14 | set(OPTIMIZATION_FLAGS "-O0 -DDEBUG")
15 | message("-- Configuring debug build")
16 | else()
17 | set(OPTIMIZATION_FLAGS "-O3 -DNDEBUG")
18 | message("-- Configuring release build")
19 | endif()
20 |
21 | # Enable extra warnings to adhere to https://github.com/mapbox/cpp/issues/37
22 | set(DESIRED_WARNINGS "-Wall -Wextra -Wconversion -Wunreachable-code -Wuninitialized -pedantic-errors -Wold-style-cast -Wno-error=unused-variable -Wshadow -Wfloat-equal -Weffc++")
23 | if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
24 | set(DESIRED_WARNINGS "${DESIRED_WARNINGS} -Wmost")
25 | endif()
26 |
27 | # Note: -D_GLIBCXX_USE_CXX11_ABI=0 is needed to support mason packages that are precompiled libs
28 | # Currently we only depend on a header only library, but this will help avoid issues when more libs are added via mason
29 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OPTIMIZATION_FLAGS} -D_GLIBCXX_USE_CXX11_ABI=0 ${DESIRED_WARNINGS}")
30 |
31 | if (WERROR)
32 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror")
33 | endif()
34 |
35 | # mason_use is a mason function within the mason.cmake file and provides ready-to-go vars, like "STATIC_LIBS" and "INCLUDE_DIRS"
36 | mason_use(catch VERSION 2.12.1 HEADER_ONLY)
37 | include_directories(SYSTEM ${MASON_PACKAGE_catch_INCLUDE_DIRS})
38 |
39 | mason_use(benchmark VERSION 1.4.1)
40 | include_directories(SYSTEM ${MASON_PACKAGE_benchmark_INCLUDE_DIRS})
41 |
42 | include_directories("${PROJECT_SOURCE_DIR}/include")
43 |
44 | file(GLOB TEST_SOURCES test/*.cpp)
45 | add_executable(unit-tests ${TEST_SOURCES})
46 |
47 | # libbenchmark.a supports threads and therefore needs pthread support
48 | find_package(Threads REQUIRED)
49 | file(GLOB BENCH_SOURCES bench/*.cpp)
50 | add_executable(bench-tests ${BENCH_SOURCES})
51 |
52 | # link benchmark static library to the bench-tests binary so the bench tests know where to find the benchmark impl code
53 | target_link_libraries(bench-tests ${MASON_PACKAGE_benchmark_STATIC_LIBS} ${CMAKE_THREAD_LIBS_INIT})
54 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Thanks for getting involved and contributing to the skel :tada: Below are a few things to setup when submitting a PR.
4 |
5 | ## Code comments
6 |
7 | If adding new code, be sure to include relevant code comments. Code comments are a great way for others to learn from your code. This is especially true within the skeleton, since it is made for learning.
8 |
9 | ## Update Documentation
10 |
11 | Be sure to update any documentation relevant to your change. This includes updating the [CHANGELOG.md](https://github.com/mapbox/hpp-skel/blob/master/CHANGELOG.md).
12 |
13 | ## Code Formatting
14 |
15 | We use [this script](/scripts/format.sh#L20) to install a consistent version of [`clang-format`](https://clang.llvm.org/docs/ClangFormat.html) to format the code base. The format is automatically checked via a Travis CI build as well. Run the following script locally to ensure formatting is ready to merge:
16 |
17 | make format
18 |
19 | We also use [`clang-tidy`](https://clang.llvm.org/extra/clang-tidy/) as a C++ linter. Run the following command to lint and ensure your code is ready to merge:
20 |
21 | make tidy
22 |
23 | These commands are set from within [the Makefile](./Makefile).
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | CC0 1.0 Universal
2 |
3 | Statement of Purpose
4 |
5 | The laws of most jurisdictions throughout the world automatically confer
6 | exclusive Copyright and Related Rights (defined below) upon the creator and
7 | subsequent owner(s) (each and all, an "owner") of an original work of
8 | authorship and/or a database (each, a "Work").
9 |
10 | Certain owners wish to permanently relinquish those rights to a Work for the
11 | purpose of contributing to a commons of creative, cultural and scientific
12 | works ("Commons") that the public can reliably and without fear of later
13 | claims of infringement build upon, modify, incorporate in other works, reuse
14 | and redistribute as freely as possible in any form whatsoever and for any
15 | purposes, including without limitation commercial purposes. These owners may
16 | contribute to the Commons to promote the ideal of a free culture and the
17 | further production of creative, cultural and scientific works, or to gain
18 | reputation or greater distribution for their Work in part through the use and
19 | efforts of others.
20 |
21 | For these and/or other purposes and motivations, and without any expectation
22 | of additional consideration or compensation, the person associating CC0 with a
23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
25 | and publicly distribute the Work under its terms, with knowledge of his or her
26 | Copyright and Related Rights in the Work and the meaning and intended legal
27 | effect of CC0 on those rights.
28 |
29 | 1. Copyright and Related Rights. A Work made available under CC0 may be
30 | protected by copyright and related or neighboring rights ("Copyright and
31 | Related Rights"). Copyright and Related Rights include, but are not limited
32 | to, the following:
33 |
34 | i. the right to reproduce, adapt, distribute, perform, display, communicate,
35 | and translate a Work;
36 |
37 | ii. moral rights retained by the original author(s) and/or performer(s);
38 |
39 | iii. publicity and privacy rights pertaining to a person's image or likeness
40 | depicted in a Work;
41 |
42 | iv. rights protecting against unfair competition in regards to a Work,
43 | subject to the limitations in paragraph 4(a), below;
44 |
45 | v. rights protecting the extraction, dissemination, use and reuse of data in
46 | a Work;
47 |
48 | vi. database rights (such as those arising under Directive 96/9/EC of the
49 | European Parliament and of the Council of 11 March 1996 on the legal
50 | protection of databases, and under any national implementation thereof,
51 | including any amended or successor version of such directive); and
52 |
53 | vii. other similar, equivalent or corresponding rights throughout the world
54 | based on applicable law or treaty, and any national implementations thereof.
55 |
56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of,
57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
59 | and Related Rights and associated claims and causes of action, whether now
60 | known or unknown (including existing as well as future claims and causes of
61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum
62 | duration provided by applicable law or treaty (including future time
63 | extensions), (iii) in any current or future medium and for any number of
64 | copies, and (iv) for any purpose whatsoever, including without limitation
65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
66 | the Waiver for the benefit of each member of the public at large and to the
67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver
68 | shall not be subject to revocation, rescission, cancellation, termination, or
69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work
70 | by the public as contemplated by Affirmer's express Statement of Purpose.
71 |
72 | 3. Public License Fallback. Should any part of the Waiver for any reason be
73 | judged legally invalid or ineffective under applicable law, then the Waiver
74 | shall be preserved to the maximum extent permitted taking into account
75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
76 | is so judged Affirmer hereby grants to each affected person a royalty-free,
77 | non transferable, non sublicensable, non exclusive, irrevocable and
78 | unconditional license to exercise Affirmer's Copyright and Related Rights in
79 | the Work (i) in all territories worldwide, (ii) for the maximum duration
80 | provided by applicable law or treaty (including future time extensions), (iii)
81 | in any current or future medium and for any number of copies, and (iv) for any
82 | purpose whatsoever, including without limitation commercial, advertising or
83 | promotional purposes (the "License"). The License shall be deemed effective as
84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the
85 | License for any reason be judged legally invalid or ineffective under
86 | applicable law, such partial invalidity or ineffectiveness shall not
87 | invalidate the remainder of the License, and in such case Affirmer hereby
88 | affirms that he or she will not (i) exercise any of his or her remaining
89 | Copyright and Related Rights in the Work or (ii) assert any associated claims
90 | and causes of action with respect to the Work, in either case contrary to
91 | Affirmer's express Statement of Purpose.
92 |
93 | 4. Limitations and Disclaimers.
94 |
95 | a. No trademark or patent rights held by Affirmer are waived, abandoned,
96 | surrendered, licensed or otherwise affected by this document.
97 |
98 | b. Affirmer offers the Work as-is and makes no representations or warranties
99 | of any kind concerning the Work, express, implied, statutory or otherwise,
100 | including without limitation warranties of title, merchantability, fitness
101 | for a particular purpose, non infringement, or the absence of latent or
102 | other defects, accuracy, or the present or absence of errors, whether or not
103 | discoverable, all to the greatest extent permissible under applicable law.
104 |
105 | c. Affirmer disclaims responsibility for clearing rights of other persons
106 | that may apply to the Work or any use thereof, including without limitation
107 | any person's Copyright and Related Rights in the Work. Further, Affirmer
108 | disclaims responsibility for obtaining any necessary consents, permissions
109 | or other rights required for any use of the Work.
110 |
111 | d. Affirmer understands and acknowledges that Creative Commons is not a
112 | party to this document and has no duty or obligation with respect to this
113 | CC0 or use of the Work.
114 |
115 | For more information, please see
116 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 |
2 | # Whether to turn compiler warnings into errors
3 | export WERROR ?= true
4 | export BUILD_DIR ?= cmake-build
5 |
6 | default: release
7 |
8 | release:
9 | mkdir -p ./$(BUILD_DIR) && cd ./$(BUILD_DIR) && cmake ../ -DCMAKE_BUILD_TYPE=Release -DWERROR=$(WERROR) && VERBOSE=1 cmake --build .
10 |
11 | debug:
12 | mkdir -p ./$(BUILD_DIR) && cd ./$(BUILD_DIR) && cmake ../ -DCMAKE_BUILD_TYPE=Debug -DWERROR=$(WERROR) && VERBOSE=1 cmake --build .
13 |
14 | test:
15 | @if [ -f ./$(BUILD_DIR)/unit-tests ]; then ./$(BUILD_DIR)/unit-tests; else echo "Please run 'make release' or 'make debug' first" && exit 1; fi
16 |
17 | bench:
18 | @if [ -f ./$(BUILD_DIR)/bench-tests ]; then ./$(BUILD_DIR)/bench-tests; else echo "Please run 'make release' or 'make debug' first" && exit 1; fi
19 |
20 | tidy:
21 | ./scripts/clang-tidy.sh
22 |
23 | coverage:
24 | ./scripts/coverage.sh
25 |
26 | clean:
27 | rm -rf ./$(BUILD_DIR)
28 | # remove remains from running 'make coverage'
29 | rm -f *.profraw
30 | rm -f *.profdata
31 | @echo "run 'make distclean' to also clear mason_packages, .mason, and .toolchain directories"
32 |
33 | distclean: clean
34 | rm -rf mason_packages
35 | # remove remains from running './scripts/setup.sh'
36 | rm -rf .mason
37 | rm -rf .toolchain
38 | rm -f local.env
39 |
40 | format:
41 | ./scripts/format.sh
42 |
43 | .PHONY: test bench
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Skeleton for C++ header-only libraries that can be included into other C++ projects. This repository itself is a helper library. To use it for a specific project, edit filenames and tests accordingly.
2 |
3 | [](https://travis-ci.com/mapbox/hpp-skel)
4 | [](https://codecov.io/gh/mapbox/hpp-skel)
5 |
6 | 
7 |
8 | ## Usage
9 |
10 | ```cpp
11 | #include
12 |
13 | using namespace hello_world;
14 |
15 | // exclaim a string
16 | std::string value = exclaim("hello");
17 | std::clog << value; // => hello!
18 | ```
19 |
20 | ## Test
21 |
22 | ```shell
23 | # build test binaries
24 | make
25 |
26 | # run tests
27 | make test
28 |
29 | # run bench tests
30 | make bench
31 | ```
32 |
33 | The default test binaries will be built in release mode. You can make Debug test binaries as well:
34 |
35 | ```shell
36 | make clean
37 | make debug
38 | make test
39 | ```
40 |
41 | Enable additional sanitizers to catch hard-to-find bugs, for example:
42 |
43 | ```shell
44 | export LDFLAGS="-fsanitize=address,undefined,integer"
45 | export CXXFLAGS="-fsanitize=address,undefined,integer"
46 |
47 | make
48 | ```
49 |
50 | ## Customize
51 | Easily use this skeleton as a starting off point.
52 |
53 | ### Start new project
54 | ```
55 | # Clone hpp-skel locally
56 |
57 | git clone git@github.com:mapbox/hpp-skel.git
58 | cd hpp-skel/
59 |
60 | # Create your new repo on GitHub and have the remote repo url handy for liftoff
61 | # Then run the liftoff script from within your local hpp-skel root directory.
62 | #
63 | # This will:
64 | # - prompt you for the new name of your project and the new remote repo url
65 | # - automatically create a new directory for your new project repo
66 | # - create a new branch called "hpp-skel-port" within your new repo directory
67 | # - add, commit, and push that branch to your new repo
68 |
69 | ./scripts/liftoff.sh
70 |
71 | ```
72 |
73 | ### Add custom code
74 | Once your project has ported hpp-skel, follow these steps to integrate your own code:
75 |
76 | - Create a dir in `./include` to hold your custom code. See the example code within [`./include`](https://github.com/mapbox/hpp-skel/tree/master/include) for reference.
77 | - Create a new file within your new directory, and add your custom method or class.
78 | - Create a module header file (see [hello_world.hpp](https://github.com/mapbox/hpp-skel/blob/master/include/hello_world.hpp)), and `#include` your new method or class. Make sure this file matches the name of the directory you created in the first step above.
79 | - Run `make` and see what surprises await on your new journey :boat:
80 | - If it compiles succesfully, congrats :tada: If not, dont fret. Take a breath and read the error message.
81 | - To start putting your header lib to work, setup a test to make sure it is working as expected.
82 |
83 | ### Setup tests
84 | Since header-only libraries are _not_ normally compiled themselves, to test them you need to [#include](https://github.com/mapbox/cpp/blob/master/glossary.md#include) them in a `.cpp` file (aka a [translation unit](https://github.com/mapbox/cpp/blob/master/glossary.md#translation-unit)) to compile and run their code. We recommend using [Catch](https://github.com/catchorg/Catch2) to make writing this `.cpp` file easy.
85 |
86 | - Create a file in `/test` directory, and add the following (be sure to update relevant lines with the name of the header you created above):
87 | ``` cpp
88 | #include
89 | #define CATCH_CONFIG_MAIN
90 | #include
91 |
92 | TEST_CASE("test_my_header")
93 | {
94 | // Your test logic here
95 | }
96 | ```
97 | - Fill in the TEST_CASE with relevant [Catch](https://github.com/catchorg/Catch2) logic (see [test.cpp](https://github.com/mapbox/hpp-skel/blob/master/test/test.cpp) for examples).
98 | - Tip: When calling your header method/class, make sure the namespace matches your header. For example
99 | ``` cpp
100 | // "hello_world" is the namespace
101 | // "exclaim" is the method
102 |
103 | std::string value = hello_world::exclaim("hello");
104 | ```
105 | - Run `make test` to compile and run your test
106 |
107 | ## Benchmarks
108 | This skeleton uses [Google Benchmark](https://github.com/google/benchmark) to measure performance, and includes a [couple benchmark tests](https://github.com/mapbox/hpp-skel/blob/master/bench/run.cpp) to get you up and running quickly:
109 | - `BM_exlaim()`: Pretty barebone, simply testing string creation
110 | - `BM_expensive()`: Expensive allocation of std::map, querying, and string comparison, therefore threads are busy. This benchmark accepts an arg to specify amount of work you'd like the script to do (how big the map will be, how many times to convert an int to a string, and how many times to run the map lookup)
111 |
112 | #### Some notes on Google Benchmark results:
113 | - Number of Iterations is automatically set, based on how many iterations it takes to obtain sufficient data for the bench.
114 | - Results provide both wall time and CPU time. You may notice, the more threads used for the expensive code (up until the number of available cores on the machine), the longer CPU time will be and the shorter wall time will be. This is because the more code is shared between threads, the faster it can run in total time; the more threads doing work, the more processes running, therefore longer CPU time.
115 |
116 | #### Compiler Optimization
117 | To obtain a true benchmark, it may be necessary to prevent the compiler from optimizing away a value or expression. The skeleton uses Google Benchmark's internal functions to manage this. See https://github.com/google/benchmark#preventing-optimisation for more details.
118 |
119 | ## Publishing
120 |
121 | We recommend publishing header files to [Mason](https://github.com/mapbox/mason), the C++ packaging manager. Binaries can be downloaded by project name and version number. In order to publish to Mason you must request the publish via a Pull Request to the [`scripts/` directory](https://github.com/mapbox/mason/tree/master/scripts) with your project materials.
122 |
123 | Mason packages can be downloaded to your project by using the `mason install` command. This is best set up in a Makefile ([example](https://github.com/mapbox/geometry.hpp/blob/23b7fe66b11a4b7830c797817efe19660806d851/Makefile#L10)).
124 |
125 | Of course, you can always copy and paste this repo into your vendor path for your project. :scissors:
126 |
127 | ## Versioning
128 |
129 | This library is semantically versioned using the /include/hello_world/version.cpp file. This defines a number of macros that can be used to check the current major, minor, or patch versions, as well as the full version string.
130 |
131 | Here's how a downstream client would check for a particular version to use specific API methods
132 | ```cpp
133 | #if HELLOWORLD_VERSION_MAJOR > 2
134 | // use version 2 api
135 | #else
136 | // use older verion apis
137 | #endif
138 | ```
139 |
140 | Here's how to check the version string
141 | ```cpp
142 | std::cout << "version: " << HELLOWORLD_VERSION_STRING << "/n";
143 | // => version: 0.2.0
144 | ```
145 |
146 | And lastly, mathematically checking for a specific version:
147 | ```cpp
148 | #if HELLOWORLD_VERSION_CODE > 20001
149 | // use feature provided in v2.0.1
150 | #endif
151 | ```
152 |
153 | # Contributing and License
154 |
155 | Contributors are welcome! :sparkles: This repo exists as a place to gather C++ Header Library knowledge that will benefit the larger community. Please contribute your knowledge if you'd like.
156 |
157 | hpp-skel is licensed under [CC0](https://creativecommons.org/share-your-work/public-domain/cc0/). Attribution is not required, but definitely welcome! If your project uses this skeleton, please add the hpp-skel badge to your readme so that others can learn about the resource.
158 |
159 | [](https://github.com/mapbox/hpp-skel)
160 |
161 | To include the badge, paste this into your README.md file:
162 | ```
163 | [](https://github.com/mapbox/hpp-skel)
164 | ```
165 |
166 | See [CONTRIBUTING](CONTRIBUTING.md) and [LICENSE](LICENSE.md) for more info.
167 |
--------------------------------------------------------------------------------
/bench/run.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | static void BM_expensive(benchmark::State& state) // NOLINT google-runtime-references
5 | {
6 | if (state.thread_index == 0)
7 | {
8 | // Setup code here.
9 | }
10 | while (state.KeepRunning())
11 | {
12 | std::string value = hello_world::expensive(static_cast(state.range(0)));
13 | benchmark::DoNotOptimize(value.data());
14 | benchmark::ClobberMemory();
15 | }
16 | if (state.thread_index == 0)
17 | {
18 | // Teardown code here.
19 | }
20 | }
21 |
22 | static void BM_exclaim(benchmark::State& state) // NOLINT google-runtime-references
23 | {
24 | while (state.KeepRunning())
25 | {
26 | std::string value = hello_world::exclaim("hello");
27 | benchmark::DoNotOptimize(value.data());
28 | benchmark::ClobberMemory();
29 | }
30 | }
31 |
32 | auto main(int argc, char* argv[]) -> int
33 | {
34 | benchmark::RegisterBenchmark("BM_exclaim", BM_exclaim)->Threads(2)->Threads(4)->Threads(8); // NOLINT clang-analyzer-cplusplus.NewDeleteLeaks
35 | benchmark::RegisterBenchmark("BM_expensive", BM_expensive)->Threads(2)->Threads(4)->Threads(8)->Arg(1000000); // NOLINT clang-analyzer-cplusplus.NewDeleteLeaks
36 | benchmark::Initialize(&argc, argv);
37 | benchmark::RunSpecifiedBenchmarks();
38 |
39 | return 0;
40 | }
--------------------------------------------------------------------------------
/cmake/mason.cmake:
--------------------------------------------------------------------------------
1 | string(RANDOM LENGTH 16 MASON_INVOCATION)
2 |
3 | # Directory where Mason packages are located; typically ends with mason_packages
4 | if (NOT MASON_PACKAGE_DIR)
5 | set(MASON_PACKAGE_DIR "${CMAKE_SOURCE_DIR}/mason_packages")
6 | endif()
7 |
8 | # URL prefix of where packages are located.
9 | if (NOT MASON_REPOSITORY)
10 | set(MASON_REPOSITORY "https://mason-binaries.s3.amazonaws.com")
11 | endif()
12 |
13 | # Path to Mason executable
14 | if (NOT MASON_COMMAND)
15 | set(MASON_COMMAND "${CMAKE_SOURCE_DIR}/.mason/mason")
16 | endif()
17 |
18 | # Determine platform
19 | # we call uname -s manually here since
20 | # CMAKE_HOST_SYSTEM_NAME will not be defined before the project() call
21 | execute_process(
22 | COMMAND uname -s
23 | OUTPUT_VARIABLE UNAME_S
24 | OUTPUT_STRIP_TRAILING_WHITESPACE)
25 |
26 | if(NOT MASON_PLATFORM)
27 | if (UNAME_S STREQUAL "Darwin")
28 | set(MASON_PLATFORM "macos")
29 | else()
30 | set(MASON_PLATFORM "linux")
31 | endif()
32 | endif()
33 |
34 |
35 | # Determine platform version string
36 | if(MASON_PLATFORM STREQUAL "ios")
37 | set(MASON_PLATFORM_VERSION "8.0") # Deployment target version
38 | elseif(MASON_PLATFORM STREQUAL "android")
39 | if (ANDROID_ABI STREQUAL "armeabi")
40 | set(MASON_PLATFORM_VERSION "arm-v5-9")
41 | elseif(ANDROID_ABI STREQUAL "arm64-v8a")
42 | set(MASON_PLATFORM_VERSION "arm-v8-21")
43 | elseif(ANDROID_ABI STREQUAL "x86")
44 | set(MASON_PLATFORM_VERSION "x86-9")
45 | elseif(ANDROID_ABI STREQUAL "x86_64")
46 | set(MASON_PLATFORM_VERSION "x86-64-21")
47 | elseif(ANDROID_ABI STREQUAL "mips")
48 | set(MASON_PLATFORM_VERSION "mips-9")
49 | elseif(ANDROID_ABI STREQUAL "mips64")
50 | set(MASON_PLATFORM_VERSION "mips64-21")
51 | else()
52 | set(MASON_PLATFORM_VERSION "arm-v7-9")
53 | endif()
54 | elseif(NOT MASON_PLATFORM_VERSION)
55 | execute_process(
56 | COMMAND uname -m
57 | OUTPUT_VARIABLE MASON_PLATFORM_VERSION
58 | OUTPUT_STRIP_TRAILING_WHITESPACE)
59 | endif()
60 |
61 | if(MASON_PLATFORM STREQUAL "macos")
62 | set(MASON_PLATFORM "osx")
63 | endif()
64 |
65 | set(ENV{MASON_PLATFORM} "${MASON_PLATFORM}")
66 | set(ENV{MASON_PLATFORM_VERSION} "${MASON_PLATFORM_VERSION}")
67 |
68 | include(CMakeParseArguments)
69 |
70 | function(mason_use _PACKAGE)
71 | if(NOT _PACKAGE)
72 | message(FATAL_ERROR "[Mason] No package name given")
73 | endif()
74 |
75 | cmake_parse_arguments("" "HEADER_ONLY" "VERSION" "" ${ARGN})
76 |
77 | if(_UNPARSED_ARGUMENTS)
78 | message(FATAL_ERROR "[Mason] mason_use() called with unrecognized arguments: ${_UNPARSED_ARGUMENTS}")
79 | endif()
80 |
81 | if(NOT _VERSION)
82 | message(FATAL_ERROR "[Mason] Specifying a version is required")
83 | endif()
84 |
85 | if(MASON_PACKAGE_${_PACKAGE}_INVOCATION STREQUAL "${MASON_INVOCATION}")
86 | # Check that the previous invocation of mason_use didn't select another version of this package
87 | if(NOT MASON_PACKAGE_${_PACKAGE}_VERSION STREQUAL ${_VERSION})
88 | message(FATAL_ERROR "[Mason] Already using ${_PACKAGE} ${MASON_PACKAGE_${_PACKAGE}_VERSION}. Cannot select version ${_VERSION}.")
89 | endif()
90 | else()
91 | if(_HEADER_ONLY)
92 | set(_PLATFORM_ID "headers")
93 | else()
94 | set(_PLATFORM_ID "${MASON_PLATFORM}-${MASON_PLATFORM_VERSION}")
95 | endif()
96 |
97 | set(_SLUG "${_PLATFORM_ID}/${_PACKAGE}/${_VERSION}")
98 | set(_INSTALL_PATH "${MASON_PACKAGE_DIR}/${_SLUG}")
99 | file(RELATIVE_PATH _INSTALL_PATH_RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${_INSTALL_PATH}")
100 |
101 | if(NOT EXISTS "${_INSTALL_PATH}")
102 | set(_CACHE_PATH "${MASON_PACKAGE_DIR}/.binaries/${_SLUG}.tar.gz")
103 | if (NOT EXISTS "${_CACHE_PATH}")
104 | # Download the package
105 | set(_URL "${MASON_REPOSITORY}/${_SLUG}.tar.gz")
106 | message(STATUS "[Mason] Downloading package ${_URL}...")
107 |
108 | set(_FAILED)
109 | set(_ERROR)
110 | # Note: some CMake versions are compiled without SSL support
111 | get_filename_component(_CACHE_DIR "${_CACHE_PATH}" DIRECTORY)
112 | file(MAKE_DIRECTORY "${_CACHE_DIR}")
113 | execute_process(
114 | COMMAND curl --retry 3 -s -f -S -L "${_URL}" -o "${_CACHE_PATH}.tmp"
115 | RESULT_VARIABLE _FAILED
116 | ERROR_VARIABLE _ERROR)
117 | if(_FAILED)
118 | message(FATAL_ERROR "[Mason] Failed to download ${_URL}: ${_ERROR}")
119 | else()
120 | # We downloaded to a temporary file to prevent half-finished downloads
121 | file(RENAME "${_CACHE_PATH}.tmp" "${_CACHE_PATH}")
122 | endif()
123 | endif()
124 |
125 | # Unpack the package
126 | message(STATUS "[Mason] Unpacking package to ${_INSTALL_PATH_RELATIVE}...")
127 | file(MAKE_DIRECTORY "${_INSTALL_PATH}")
128 | execute_process(
129 | COMMAND ${CMAKE_COMMAND} -E tar xzf "${_CACHE_PATH}"
130 | WORKING_DIRECTORY "${_INSTALL_PATH}")
131 | endif()
132 |
133 | # Create a config file if it doesn't exist in the package
134 | # TODO: remove this once all packages have a mason.ini file
135 | if(NOT EXISTS "${_INSTALL_PATH}/mason.ini")
136 | # Change pkg-config files
137 | file(GLOB_RECURSE _PKGCONFIG_FILES "${_INSTALL_PATH}/*.pc")
138 | foreach(_PKGCONFIG_FILE IN ITEMS ${_PKGCONFIG_FILES})
139 | file(READ "${_PKGCONFIG_FILE}" _PKGCONFIG_FILE_CONTENT)
140 | string(REGEX REPLACE "(^|\n)prefix=[^\n]*" "\\1prefix=${_INSTALL_PATH}" _PKGCONFIG_FILE_CONTENT "${_PKGCONFIG_FILE_CONTENT}")
141 | file(WRITE "${_PKGCONFIG_FILE}" "${_PKGCONFIG_FILE_CONTENT}")
142 | endforeach()
143 |
144 | if(NOT EXISTS "${MASON_COMMAND}")
145 | message(FATAL_ERROR "[Mason] Could not find Mason command at ${MASON_COMMAND}")
146 | endif()
147 |
148 | set(_FAILED)
149 | set(_ERROR)
150 | execute_process(
151 | COMMAND ${MASON_COMMAND} config ${_PACKAGE} ${_VERSION}
152 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
153 | OUTPUT_FILE "${_INSTALL_PATH}/mason.ini"
154 | RESULT_VARIABLE _FAILED
155 | ERROR_VARIABLE _ERROR)
156 | if(_FAILED)
157 | message(FATAL_ERROR "[Mason] Could not get configuration for package ${_PACKAGE} ${_VERSION}: ${_ERROR}")
158 | endif()
159 | endif()
160 |
161 | set(MASON_PACKAGE_${_PACKAGE}_PREFIX "${_INSTALL_PATH}" CACHE STRING "${_PACKAGE} ${_INSTALL_PATH}" FORCE)
162 | mark_as_advanced(MASON_PACKAGE_${_PACKAGE}_PREFIX)
163 |
164 | # Load the configuration from the ini file
165 | file(STRINGS "${_INSTALL_PATH}/mason.ini" _CONFIG_FILE)
166 | foreach(_LINE IN LISTS _CONFIG_FILE)
167 | string(REGEX MATCH "^([a-z_]+) *= *" _KEY "${_LINE}")
168 | if (_KEY)
169 | string(LENGTH "${_KEY}" _KEY_LENGTH)
170 | string(SUBSTRING "${_LINE}" ${_KEY_LENGTH} -1 _VALUE)
171 | string(REGEX REPLACE ";.*$" "" _VALUE "${_VALUE}") # Trim trailing commas
172 | string(REPLACE "{prefix}" "${_INSTALL_PATH}" _VALUE "${_VALUE}")
173 | string(STRIP "${_VALUE}" _VALUE)
174 | string(REPLACE "=" "" _KEY "${_KEY}")
175 | string(STRIP "${_KEY}" _KEY)
176 | string(TOUPPER "${_KEY}" _KEY)
177 | if(_KEY STREQUAL "INCLUDE_DIRS" OR _KEY STREQUAL "STATIC_LIBS" )
178 | separate_arguments(_VALUE)
179 | endif()
180 | set(MASON_PACKAGE_${_PACKAGE}_${_KEY} "${_VALUE}" CACHE STRING "${_PACKAGE} ${_KEY}" FORCE)
181 | mark_as_advanced(MASON_PACKAGE_${_PACKAGE}_${_KEY})
182 | endif()
183 | endforeach()
184 |
185 | # Compare version in the package to catch errors early on
186 | if(NOT _VERSION STREQUAL MASON_PACKAGE_${_PACKAGE}_VERSION)
187 | message(FATAL_ERROR "[Mason] Package at ${_INSTALL_PATH_RELATIVE} has version '${MASON_PACKAGE_${_PACKAGE}_VERSION}', but required '${_VERSION}'")
188 | endif()
189 |
190 | if(NOT _PACKAGE STREQUAL MASON_PACKAGE_${_PACKAGE}_NAME)
191 | message(FATAL_ERROR "[Mason] Package at ${_INSTALL_PATH_RELATIVE} has name '${MASON_PACKAGE_${_PACKAGE}_NAME}', but required '${_NAME}'")
192 | endif()
193 |
194 | if(NOT _HEADER_ONLY)
195 | if(NOT MASON_PLATFORM STREQUAL MASON_PACKAGE_${_PACKAGE}_PLATFORM)
196 | message(FATAL_ERROR "[Mason] Package at ${_INSTALL_PATH_RELATIVE} has platform '${MASON_PACKAGE_${_PACKAGE}_PLATFORM}', but required '${MASON_PLATFORM}'")
197 | endif()
198 |
199 | if(NOT MASON_PLATFORM_VERSION STREQUAL MASON_PACKAGE_${_PACKAGE}_PLATFORM_VERSION)
200 | message(FATAL_ERROR "[Mason] Package at ${_INSTALL_PATH_RELATIVE} has platform version '${MASON_PACKAGE_${_PACKAGE}_PLATFORM_VERSION}', but required '${MASON_PLATFORM_VERSION}'")
201 | endif()
202 | endif()
203 |
204 | # Concatenate the static libs and libraries
205 | set(_LIBRARIES)
206 | list(APPEND _LIBRARIES ${MASON_PACKAGE_${_PACKAGE}_STATIC_LIBS} ${MASON_PACKAGE_${_PACKAGE}_LDFLAGS})
207 | set(MASON_PACKAGE_${_PACKAGE}_LIBRARIES "${_LIBRARIES}" CACHE STRING "${_PACKAGE} _LIBRARIES" FORCE)
208 | mark_as_advanced(MASON_PACKAGE_${_PACKAGE}_LIBRARIES)
209 |
210 | if(NOT _HEADER_ONLY)
211 | string(REGEX MATCHALL "(^| +)-L *([^ ]+)" MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS "${MASON_PACKAGE_${_PACKAGE}_LDFLAGS}")
212 | string(REGEX REPLACE "(^| +)-L *" "\\1" MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS "${MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS}")
213 | set(MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS "${MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS}" CACHE STRING "${_PACKAGE} ${MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS}" FORCE)
214 | mark_as_advanced(MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS)
215 | endif()
216 |
217 | # Store invocation ID to prevent different versions of the same package in one invocation
218 | set(MASON_PACKAGE_${_PACKAGE}_INVOCATION "${MASON_INVOCATION}" CACHE INTERNAL "${_PACKAGE} invocation ID" FORCE)
219 | endif()
220 | endfunction()
221 |
222 | macro(target_add_mason_package _TARGET _VISIBILITY _PACKAGE)
223 | if (NOT MASON_PACKAGE_${_PACKAGE}_INVOCATION)
224 | message(FATAL_ERROR "[Mason] Package ${_PACKAGE} has not been initialized yet")
225 | endif()
226 |
227 | target_include_directories(${_TARGET} ${_VISIBILITY} "${MASON_PACKAGE_${_PACKAGE}_INCLUDE_DIRS}")
228 | target_compile_definitions(${_TARGET} ${_VISIBILITY} "${MASON_PACKAGE_${_PACKAGE}_DEFINITIONS}")
229 | target_compile_options(${_TARGET} ${_VISIBILITY} "${MASON_PACKAGE_${_PACKAGE}_OPTIONS}")
230 | target_link_libraries(${_TARGET} ${_VISIBILITY} "${MASON_PACKAGE_${_PACKAGE}_LIBRARIES}")
231 | endmacro()
232 |
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | ignore:
2 | - "bench"
3 | - "test"
--------------------------------------------------------------------------------
/include/hello_world.hpp:
--------------------------------------------------------------------------------
1 | #ifndef HELLO_WORLD_HPP
2 | #define HELLO_WORLD_HPP
3 |
4 | /* The hello_world namespace.
5 | *
6 | * Hello World implements a standard namespace with a few available functions.
7 | */
8 | #include "hello_world/exclaim.hpp"
9 | #include "hello_world/expensive.hpp"
10 | #include "hello_world/version.hpp"
11 |
12 | #endif
13 |
--------------------------------------------------------------------------------
/include/hello_world/exclaim.hpp:
--------------------------------------------------------------------------------
1 | #ifndef HELLO_WORLD_EXCLAIM_HPP
2 | #define HELLO_WORLD_EXCLAIM_HPP
3 |
4 | #include
5 |
6 | namespace hello_world {
7 |
8 | /* Exclaim a string. Part of the namespace.
9 | * @message the message you would like to exclaim.
10 | *
11 | * exclaim adds an exclamation point to the beginning and end of a string.
12 | *
13 | * @return your message with an exclamation point on each end.
14 | */
15 | inline auto exclaim(const std::string& message) -> std::string
16 | {
17 | std::string response = message + "!";
18 | return response;
19 | }
20 |
21 | } // namespace hello_world
22 |
23 | #endif
24 |
--------------------------------------------------------------------------------
/include/hello_world/expensive.hpp:
--------------------------------------------------------------------------------
1 | #ifndef HELLO_WORLD_EXPENSIVE_HPP
2 | #define HELLO_WORLD_EXPENSIVE_HPP
3 |
4 | #include
5 | #include