├── .clang-format ├── .github └── workflows │ ├── amalgamate.yml │ ├── cmake-builds-mac-linux.yml │ ├── publish.yml │ └── release.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── ci └── config_compiler.sh ├── include └── gos │ ├── concepts │ ├── collection.hpp │ ├── container.hpp │ └── pair.hpp │ ├── config │ ├── concept.hpp │ ├── cpp_features.hpp │ └── type_traits.hpp │ ├── conversions │ ├── ostream.hpp │ └── string.hpp │ └── goose.hpp └── test ├── CMakeLists.txt ├── array.cpp ├── catch.hpp ├── main.cpp ├── pair.cpp ├── string.cpp ├── unordered_map.cpp └── vector.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | Standard: Auto 4 | AccessModifierOffset: -4 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: None 7 | AlignConsecutiveDeclarations: None 8 | AlignConsecutiveMacros: None 9 | AlignEscapedNewlines: DontAlign 10 | AlignOperands: true 11 | AlignTrailingComments: false 12 | AllowAllArgumentsOnNextLine: false 13 | AllowAllConstructorInitializersOnNextLine: false 14 | AllowAllParametersOfDeclarationOnNextLine: false 15 | AllowShortBlocksOnASingleLine: Empty 16 | AllowShortCaseLabelsOnASingleLine: false 17 | AllowShortFunctionsOnASingleLine: Empty 18 | AllowShortIfStatementsOnASingleLine: Never 19 | AllowShortLambdasOnASingleLine: Inline 20 | AllowShortLoopsOnASingleLine: false 21 | AlwaysBreakAfterReturnType: None 22 | AlwaysBreakBeforeMultilineStrings: false 23 | AlwaysBreakTemplateDeclarations: Yes 24 | BinPackArguments: false 25 | BinPackParameters: false 26 | BreakBeforeBraces: Custom 27 | BraceWrapping: 28 | AfterCaseLabel: false 29 | AfterClass: false 30 | AfterEnum: false 31 | AfterFunction: false 32 | AfterNamespace: false 33 | AfterObjCDeclaration: false 34 | AfterStruct: false 35 | AfterUnion: false 36 | AfterExternBlock: false 37 | BeforeCatch: false 38 | BeforeElse: false 39 | IndentBraces: false 40 | SplitEmptyFunction: false 41 | SplitEmptyRecord: false 42 | SplitEmptyNamespace: false 43 | BreakBeforeBinaryOperators: None 44 | BreakBeforeTernaryOperators: false 45 | BreakConstructorInitializers: AfterColon 46 | BreakInheritanceList: AfterColon 47 | BreakStringLiterals: true 48 | ColumnLimit: 100 49 | CompactNamespaces: false 50 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 51 | ConstructorInitializerIndentWidth: 4 52 | ContinuationIndentWidth: 4 53 | Cpp11BracedListStyle: true 54 | DerivePointerAlignment: false 55 | DisableFormat: false 56 | FixNamespaceComments: true 57 | IncludeBlocks: Preserve 58 | IndentCaseLabels: false 59 | IndentPPDirectives: None 60 | IndentWidth: 4 61 | IndentWrappedFunctionNames: false 62 | KeepEmptyLinesAtTheStartOfBlocks: false 63 | MaxEmptyLinesToKeep: 1 64 | NamespaceIndentation: None 65 | PenaltyBreakAssignment: 1000 66 | PenaltyExcessCharacter: 1000 67 | PointerAlignment: Left 68 | ReflowComments: true 69 | SortIncludes: CaseInsensitive 70 | SortUsingDeclarations: true 71 | SpaceAfterCStyleCast: true 72 | SpaceAfterLogicalNot: false 73 | SpaceAfterTemplateKeyword: false 74 | SpaceBeforeAssignmentOperators: true 75 | SpaceBeforeCtorInitializerColon: true 76 | SpaceBeforeInheritanceColon: true 77 | SpaceBeforeParens: ControlStatements 78 | SpaceBeforeRangeBasedForLoopColon: true 79 | SpaceInEmptyParentheses: false 80 | SpacesBeforeTrailingComments: 1 81 | SpacesInAngles: false 82 | SpacesInCStyleCastParentheses: false 83 | SpacesInParentheses: false 84 | SpacesInSquareBrackets: false 85 | TabWidth: 4 86 | UseTab: Never 87 | --- 88 | -------------------------------------------------------------------------------- /.github/workflows/amalgamate.yml: -------------------------------------------------------------------------------- 1 | name: Amalgamate Goose 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: 7 | workflow_call: 8 | 9 | jobs: 10 | amalgamate: 11 | name: Amalgamate Goose into single header 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Initial checkout 15 | uses: actions/checkout@v3 16 | 17 | - name: Install Quom to generate single header 18 | run: pip install --user quom 19 | 20 | - name: Amalgamate Goose into single header 21 | run: ~/.local/bin/quom ./include/gos/goose.hpp goose.hpp -I ./include -g SZO_GOS_.+_HPP 22 | 23 | - name: Upload single header Goose as artifact 24 | uses: actions/upload-artifact@v3 25 | with: 26 | name: goose-all 27 | path: goose.hpp 28 | -------------------------------------------------------------------------------- /.github/workflows/cmake-builds-mac-linux.yml: -------------------------------------------------------------------------------- 1 | name: CMake builds for Mac and Linux 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | workflow_call: 9 | inputs: 10 | release: 11 | required: true 12 | type: boolean 13 | workflow_dispatch: 14 | 15 | jobs: 16 | build: 17 | name: ${{matrix.os}}, ${{matrix.compiler}}, ${{matrix.build_type}}, C++${{matrix.cpp_std}} 18 | runs-on: ${{matrix.os}} 19 | env: 20 | AMALGAMATING_PASSED: ${{ github.event.workflow_run.conclusion == 'success' }} 21 | strategy: 22 | matrix: 23 | os: [ ubuntu-latest, macos-latest ] 24 | compiler: [ gcc, clang ] 25 | build_type: [ Release, Debug ] 26 | cpp_std: [ "17", "20" ] 27 | 28 | steps: 29 | - name: Initial checkout 30 | uses: actions/checkout@v3 31 | 32 | - name: "[Release] Download single header Goose" 33 | if: inputs.release 34 | uses: actions/download-artifact@v3 35 | with: 36 | name: goose-all 37 | 38 | - name: "[Release] Replace Goose with amalgamated single header" 39 | if: inputs.release 40 | run: mv -f goose.hpp ./include/gos/goose.hpp 41 | 42 | - name: Configure compiler 43 | run: ./ci/config_compiler.sh ${{matrix.compiler}} 44 | 45 | - name: Configure CMake 46 | run: > 47 | cmake -B ${{github.workspace}}/build 48 | -DCMAKE_BUILD_TYPE=${{matrix.build_type}} 49 | -DCMAKE_C_COMPILER=${{env.CC}} 50 | -DCMAKE_CXX_COMPILER=${{env.CXX}} 51 | -DGOS_BUILD_TESTS=ON 52 | -DGOS_CXX_STANDARD=${{matrix.cpp_std}} 53 | 54 | - name: Build 55 | run: cmake --build ${{github.workspace}}/build --config ${{matrix.build_type}} -- -j 4 56 | 57 | - name: Test 58 | working-directory: ${{github.workspace}}/build 59 | run: ./test/test-goose 60 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish artifacts 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: 7 | workflow_call: 8 | 9 | jobs: 10 | publish: 11 | name: Publish Goose 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Initial checkout 15 | uses: actions/checkout@v3 16 | 17 | - name: Download single header Goose 18 | uses: actions/download-artifact@v3 19 | with: 20 | name: goose-all 21 | 22 | - name: Release Goose 23 | uses: softprops/action-gh-release@v1 24 | with: 25 | files: | 26 | goose.hpp 27 | LICENSE 28 | README.md 29 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: 'Release Goose' 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | workflow_dispatch: 8 | 9 | jobs: 10 | call-amalgamate: 11 | uses: ./.github/workflows/amalgamate.yml 12 | 13 | call-cmake-builds-mac-linux: 14 | needs: call-amalgamate 15 | uses: ./.github/workflows/cmake-builds-mac-linux.yml 16 | with: 17 | release: true 18 | 19 | call-upload-artifact: 20 | needs: call-cmake-builds-mac-linux 21 | uses: ./.github/workflows/publish.yml 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | # Build directories 35 | *build*/ 36 | 37 | # Project files 38 | .idea/* 39 | 40 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.19) 2 | if(DEFINED GOS_CXX_STANDARD) 3 | set(CMAKE_CXX_STANDARD ${GOS_CXX_STANDARD}) 4 | else() 5 | set(CMAKE_CXX_STANDARD 20) 6 | endif() 7 | 8 | option(GOS_BUILD_TESTS OFF) 9 | option(GOS_USE_LIBCPP OFF) 10 | project(goose VERSION "0.1.0") 11 | 12 | add_library(goose INTERFACE) 13 | target_include_directories(goose INTERFACE 14 | $ 15 | ) 16 | 17 | if(GOS_BUILD_TESTS) 18 | add_subdirectory(test) 19 | endif() 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Szymon Zosgórnik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Goose 2 | 3 | ![Linux and Mac builds](https://github.com/SzymonZos/Goose/actions/workflows/cmake-builds-mac-linux.yml/badge.svg) 4 | 5 | Goose is a small utility library which supports inserting STL collection-like 6 | types to the output streams. It also provides stringification of such 7 | collections. 8 | 9 | ## Supported platforms 10 | 11 | Supported C++ standard versions are 17 and 20. Linux and Mac are tested on the 12 | CI. Support for Windows is not guaranteed. 13 | 14 | ## Usage 15 | 16 | 1. Goose can be used as single header library from the release version. 17 | 2. Another way of using Goose is to add this repo as a submodule. Currently, 18 | only CMake is supported as a build system. 19 | 20 | ## Examples 21 | 22 | ### Using overloaded `operator<<` for STL collections 23 | 24 | One-dimensional vector: 25 | 26 | ```c++ 27 | std::vector vec{1, 2, 3, 4}; 28 | std::cout << vec << std::endl; 29 | ``` 30 | 31 | Output: 32 | 33 | ```text 34 | [1, 2, 3, 4] 35 | ``` 36 | 37 | Two-dimensional vector (I'm not saying you should use this example in 38 | production :D): 39 | 40 | ```c++ 41 | using Vec2 = std::vector>; 42 | Vec2 vec2{{1, 2, 3, 4}, {-1, -2, -3, -4}, {42, -68}}; 43 | std::cout << vec2 << std::endl; 44 | ``` 45 | 46 | Output: 47 | 48 | ```text 49 | {[1, 2, 3, 4], 50 | [-1, -2, -3, -4], 51 | [42, -68]} 52 | ``` 53 | 54 | ### Using `gos::to_string` for STL collections 55 | 56 | ```cpp 57 | using HashMap = std::unordered_map; 58 | HashMap map{{"some", 42}, {"random", 600}, {"words", 68}}; 59 | auto stringified_map = gos::to_string(map); 60 | std::printf("Stringified map: %s\n", stringified_map.c_str()); 61 | ``` 62 | 63 | Possible output: 64 | 65 | ```text 66 | Stringified map: [[words, 68], [random, 600], [some, 42]] 67 | ``` 68 | -------------------------------------------------------------------------------- /ci/config_compiler.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -Eeuo pipefail 3 | 4 | case "$1" in 5 | *gcc*) 6 | echo "CC=$(which gcc-10)" >> "$GITHUB_ENV" 7 | echo "CXX=$(which g++-10)" >> "$GITHUB_ENV" 8 | ;; 9 | *clang*) 10 | CC="$(which clang-12)" || CC="$(brew --prefix llvm)/bin/clang" 11 | CXX="$(which clang++-12)" || CXX="$(brew --prefix llvm)/bin/clang++" 12 | echo "CC=$CC" >> "$GITHUB_ENV" 13 | echo "CXX=$CXX" >> "$GITHUB_ENV" 14 | ;; 15 | esac 16 | -------------------------------------------------------------------------------- /include/gos/concepts/collection.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SZO_GOS_COLLECTION_HPP 2 | #define SZO_GOS_COLLECTION_HPP 3 | 4 | #include "gos/config/concept.hpp" 5 | #include "gos/config/cpp_features.hpp" 6 | #include "gos/config/type_traits.hpp" 7 | 8 | #include "container.hpp" 9 | #include "pair.hpp" 10 | 11 | namespace gos { 12 | 13 | #ifdef GOS_LIB_CONCEPTS 14 | template 15 | concept collection = container || pair; 16 | 17 | #else 18 | template 19 | struct collection : std::disjunction, pair > {}; 20 | 21 | #endif // GOS_LIB_CONCEPTS 22 | } // namespace gos 23 | 24 | #endif // SZO_GOS_COLLECTION_HPP 25 | -------------------------------------------------------------------------------- /include/gos/concepts/container.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SZO_GOS_CONTAINER_HPP 2 | #define SZO_GOS_CONTAINER_HPP 3 | 4 | #include "gos/config/concept.hpp" 5 | #include "gos/config/cpp_features.hpp" 6 | #include "gos/config/type_traits.hpp" 7 | 8 | #include 9 | 10 | namespace gos { 11 | 12 | #ifdef GOS_LIB_CONCEPTS 13 | namespace detail { 14 | template 15 | concept container_impl = 16 | std::regular && std::swappable && std::destructible && 17 | std::same_as && 18 | std::same_as && 19 | std::forward_iterator && 20 | std::forward_iterator && 21 | std::signed_integral && 22 | std::same_as::difference_type> && 24 | std::same_as::difference_type> && 26 | requires(T a, const T b) { 27 | { a.begin() } -> std::same_as; 28 | { a.end() } -> std::same_as; 29 | { a.cbegin() } -> std::same_as; 30 | { a.cend() } -> std::same_as; 31 | { a == b } -> std::convertible_to; 32 | { a != b } -> std::convertible_to; 33 | { a.size() } -> std::same_as; 34 | { a.max_size() } -> std::same_as; 35 | { a.empty() } -> std::convertible_to; 36 | }; 37 | 38 | template 39 | concept container = container_impl>; 40 | 41 | template 42 | concept not_string = !std::same_as, std::string>; 43 | } // namespace detail 44 | 45 | template 46 | concept container = detail::container && detail::not_string; 47 | 48 | template 49 | concept is_container_v = container; 50 | 51 | #else 52 | namespace detail { 53 | template 54 | using container_impl = std::void_t< 55 | std::is_same().begin()), typename T::iterator>, 56 | std::is_same().end()), typename T::iterator>, 57 | std::is_same().cbegin()), typename T::const_iterator>, 58 | std::is_same().cend()), typename T::const_iterator>, 59 | std::is_same().size()), typename T::size_type>, 60 | std::is_same().max_size()), typename T::size_type>, 61 | std::is_convertible().empty()), bool>, 62 | std::is_destructible, 63 | std::is_same, 64 | std::is_same, 65 | std::is_integral, 66 | std::is_signed, 67 | std::is_same::difference_type>, 69 | std::is_same::difference_type>>; 71 | 72 | template 73 | struct container : std::false_type {}; 74 | 75 | template<> 76 | struct container : std::false_type {}; 77 | 78 | template 79 | struct container> : std::true_type {}; 80 | } // namespace detail 81 | 82 | template 83 | using container = detail::container>; 84 | 85 | template 86 | inline constexpr bool is_container_v = container::value; 87 | 88 | #endif 89 | } // namespace gos 90 | 91 | #endif // SZO_GOS_CONTAINER_HPP 92 | -------------------------------------------------------------------------------- /include/gos/concepts/pair.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SZO_GOS_PAIR_HPP 2 | #define SZO_GOS_PAIR_HPP 3 | 4 | #include "gos/config/concept.hpp" 5 | #include "gos/config/cpp_features.hpp" 6 | #include "gos/config/type_traits.hpp" 7 | 8 | namespace gos { 9 | 10 | #ifdef GOS_LIB_CONCEPTS 11 | namespace detail { 12 | template 13 | // GCC produces lvalue reference while clang non ref value 14 | concept pair = requires(T t) { 15 | { t.first } -> std::convertible_to; 16 | { t.second } -> std::convertible_to; 17 | }; 18 | } // namespace detail 19 | 20 | template 21 | concept pair = detail::pair>; 22 | 23 | template 24 | concept is_pair_v = pair; 25 | 26 | #else 27 | namespace detail { 28 | template 29 | using pair_impl = std::void_t; 30 | 31 | template 32 | struct pair : std::false_type {}; 33 | 34 | template 35 | struct pair> : std::true_type {}; 36 | } // namespace detail 37 | 38 | template 39 | using pair = detail::pair>; 40 | 41 | template 42 | inline constexpr bool is_pair_v = pair::value; 43 | 44 | #endif // GOS_LIB_CONCEPTS 45 | } // namespace gos 46 | 47 | #endif // SZO_GOS_PAIR_HPP 48 | -------------------------------------------------------------------------------- /include/gos/config/concept.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SZO_GOS_CONCEPT_HPP 2 | #define SZO_GOS_CONCEPT_HPP 3 | 4 | #include "cpp_features.hpp" 5 | 6 | #ifdef GOS_LIB_CONCEPTS 7 | #include 8 | #define GOS_CONCEPT(CONCEPT, T) template 9 | #else 10 | #define GOS_CONCEPT(CONCEPT, T) template::value>> 11 | #endif 12 | 13 | #define GOS_CONTAINER(T) GOS_CONCEPT(gos::container, T) 14 | #define GOS_PAIR(T) GOS_CONCEPT(gos::pair, T) 15 | #define GOS_COLLECTION(T) GOS_CONCEPT(gos::collection, T) 16 | 17 | #ifdef GOS_LIB_CONCEPTS 18 | #define GOS_COLLECTION_DEFINITION(T) GOS_COLLECTION(T) 19 | #else 20 | #define GOS_COLLECTION_DEFINITION(T) template 21 | #endif 22 | 23 | #endif // SZO_GOS_CONCEPT_HPP 24 | -------------------------------------------------------------------------------- /include/gos/config/cpp_features.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SZO_GOS_CPP_FEATURES_HPP 2 | #define SZO_GOS_CPP_FEATURES_HPP 3 | 4 | #if __has_include() 5 | #include 6 | #else 7 | #include 8 | #endif 9 | 10 | #ifdef __cpp_lib_remove_cvref 11 | #define GOS_LIB_REMOVE_CVREF 12 | #endif 13 | 14 | #ifdef __cpp_lib_concepts 15 | #define GOS_LIB_CONCEPTS 16 | #endif 17 | 18 | #endif // SZO_GOS_CPP_FEATURES_HPP 19 | -------------------------------------------------------------------------------- /include/gos/config/type_traits.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SZO_GOS_TYPE_TRAITS_HPP 2 | #define SZO_GOS_TYPE_TRAITS_HPP 3 | 4 | #include "cpp_features.hpp" 5 | 6 | #include 7 | 8 | namespace gos { 9 | #ifdef GOS_LIB_REMOVE_CVREF 10 | template using remove_cvref_t = std::remove_cvref_t; 11 | #else 12 | template struct remove_cvref : std::remove_cv {}; 13 | template struct remove_cvref : std::remove_cv {}; 14 | template struct remove_cvref : std::remove_cv {}; 15 | template using remove_cvref_t = typename remove_cvref::type; 16 | #endif 17 | } 18 | 19 | #endif // SZO_GOS_TYPE_TRAITS_HPP 20 | -------------------------------------------------------------------------------- /include/gos/conversions/ostream.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SZO_GOS_OSTREAM_HPP 2 | #define SZO_GOS_OSTREAM_HPP 3 | 4 | #include "gos/config/type_traits.hpp" 5 | 6 | #include "gos/concepts/collection.hpp" 7 | #include "gos/concepts/container.hpp" 8 | #include "gos/concepts/pair.hpp" 9 | 10 | #include 11 | 12 | GOS_COLLECTION(Collection) 13 | std::ostream& operator<<(std::ostream& stream, Collection&& collection); 14 | 15 | namespace gos::detail { 16 | GOS_COLLECTION(Collection) 17 | using ValueType = typename gos::remove_cvref_t::value_type; 18 | 19 | GOS_COLLECTION(Collection) 20 | std::ostream& 21 | process_collection(std::ostream& stream, Collection&& collection, std::size_t padding = 0); 22 | 23 | GOS_CONTAINER(Container) 24 | void process_scalar_container(std::ostream& stream, Container&& container) { 25 | stream << "["; 26 | for (auto it = container.begin(); it != container.end(); ++it) { 27 | stream << *it; 28 | if (std::next(it) != container.end()) { 29 | stream << ", "; 30 | } 31 | } 32 | stream << "]"; 33 | } 34 | 35 | GOS_CONTAINER(Container) 36 | void process_complex_container(std::ostream& stream, Container&& container, std::size_t padding) { 37 | stream << "{"; 38 | padding++; 39 | for (auto it = container.begin(); it != container.end(); ++it) { 40 | process_collection(stream, std::forward>(*it), padding); 41 | if (std::next(it) != container.end()) { 42 | stream << ",\n" << std::string(padding, ' '); 43 | } 44 | } 45 | stream << "}"; 46 | } 47 | 48 | GOS_CONTAINER(Container) 49 | std::ostream& process_container(std::ostream& stream, Container&& container, std::size_t padding) { 50 | using ContainerValueType = ValueType; 51 | if constexpr (std::is_scalar_v || gos::is_pair_v) { 52 | process_scalar_container(stream, std::forward(container)); 53 | } else { 54 | process_complex_container(stream, std::forward(container), padding); 55 | } 56 | return stream; 57 | } 58 | 59 | GOS_PAIR(Pair) 60 | std::ostream& process_pair(std::ostream& stream, Pair&& pair) { 61 | stream << "[" << pair.first << ", " << pair.second << "]"; 62 | return stream; 63 | } 64 | 65 | GOS_COLLECTION_DEFINITION(Collection) 66 | std::ostream& 67 | process_collection(std::ostream& stream, Collection&& collection, std::size_t padding) { 68 | if constexpr (gos::is_pair_v) { 69 | return process_pair(stream, std::forward(collection)); 70 | } else if constexpr (gos::is_container_v) { 71 | return process_container(stream, std::forward(collection), padding); 72 | } 73 | return stream; 74 | } 75 | } // namespace gos::detail 76 | 77 | GOS_COLLECTION_DEFINITION(Collection) 78 | std::ostream& operator<<(std::ostream& stream, Collection&& collection) { 79 | return gos::detail::process_collection(stream, std::forward(collection)); 80 | } 81 | 82 | #endif // SZO_GOS_OSTREAM_HPP 83 | -------------------------------------------------------------------------------- /include/gos/conversions/string.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SZO_GOS_STRING_HPP 2 | #define SZO_GOS_STRING_HPP 3 | 4 | #include "gos/concepts/collection.hpp" 5 | 6 | #include "ostream.hpp" 7 | 8 | #include 9 | #include 10 | 11 | namespace gos { 12 | namespace detail { 13 | template 14 | std::string to_string(T&& t) { 15 | std::stringstream ss; 16 | ss << std::forward(t); 17 | return ss.str(); 18 | } 19 | } // namespace detail 20 | 21 | GOS_COLLECTION(Collection) 22 | std::string to_string(Collection&& collection) { 23 | return detail::to_string(std::forward(collection)); 24 | } 25 | 26 | inline std::string to_string(const std::string& str) { 27 | return detail::to_string(str); 28 | } 29 | } // namespace gos 30 | 31 | #endif // SZO_GOS_STRING_HPP 32 | -------------------------------------------------------------------------------- /include/gos/goose.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SZO_GOOSE_HPP 2 | #define SZO_GOOSE_HPP 3 | 4 | #include "conversions/ostream.hpp" 5 | #include "conversions/string.hpp" 6 | 7 | #endif // SZO_GOOSE_HPP 8 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(GOS_TEST_SOURCES 2 | array.cpp 3 | pair.cpp 4 | string.cpp 5 | vector.cpp 6 | unordered_map.cpp 7 | ) 8 | add_executable(test-goose main.cpp ${GOS_TEST_SOURCES}) 9 | 10 | if(GOS_USE_LIBCPP) 11 | target_compile_options(test-goose PRIVATE 12 | "$<$:-stdlib=libc++>" 13 | ) 14 | set(GOS_LIBCPP_LINK_OPTIONS "-stdlib=libc++ -fuse-ld=lld -lc++abi") 15 | target_link_options(test-goose PRIVATE 16 | "$<$:${GOS_LIBCPP_LINK_OPTIONS}>" 17 | ) 18 | endif() 19 | 20 | # As for now MSYS2 does not provide ASAN support apart from clang64 toolchain 21 | # https://github.com/msys2/MINGW-packages/issues/3163 22 | # https://github.com/msys2/MINGW-packages/issues/8552 23 | if(NOT CMAKE_SYSTEM_NAME STREQUAL "Windows") 24 | set(GOS_SANITIZER_OPTIONS -fsanitize=undefined,address) 25 | target_compile_options(test-goose PRIVATE 26 | "$<$:${GOS_SANITIZER_OPTIONS}>" 27 | ) 28 | target_link_options(test-goose PRIVATE 29 | "$<$:${GOS_SANITIZER_OPTIONS}>" 30 | ) 31 | endif() 32 | 33 | target_compile_options(test-goose PRIVATE 34 | -Wall 35 | -Wextra 36 | -Werror 37 | -Wshadow # Warn if variable overshadows parent context 38 | -Wnon-virtual-dtor # Warn if class with virtual func has no virtual dtor 39 | -Wcast-align # Warn for potential performance problem casts 40 | -Wunused # Warn on anything being unused 41 | -Wno-overloaded-virtual # Don't warn if overload a virtual function 42 | -Wpedantic # Warn if non-standard C++ is used 43 | -Wconversion # Warn on type conversions that may lose data 44 | -Wsign-conversion # Warn on sign conversions 45 | -Wdouble-promotion # Warn if float is implicit promoted to double 46 | -Wold-style-cast # Warn if c style cast is performed 47 | ) 48 | target_compile_options(test-goose PRIVATE 49 | "$<$:-fconcepts-diagnostics-depth=5>" 50 | ) 51 | 52 | target_include_directories(test-goose PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) 53 | target_link_libraries(test-goose PRIVATE goose) 54 | -------------------------------------------------------------------------------- /test/array.cpp: -------------------------------------------------------------------------------- 1 | #include "gos/goose.hpp" 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace { 8 | template 9 | using EmptyArr = std::array; 10 | 11 | TEST_CASE("Empty array stringification", "[Empty]") { 12 | EmptyArr arr{}; 13 | REQUIRE(gos::to_string(arr) == "[]"); 14 | } 15 | 16 | TEST_CASE("Empty 2D array stringification", "[Empty]") { 17 | EmptyArr> arr{}; 18 | REQUIRE(gos::to_string(arr) == "{}"); 19 | } 20 | 21 | TEST_CASE("Empty 4D array stringification", "[Empty]") { 22 | EmptyArr>>> arr{}; 23 | REQUIRE(gos::to_string(arr) == "{}"); 24 | } 25 | 26 | TEST_CASE("Int array basic stringification", "[Scalar]") { 27 | std::array arr{1, 2, 3, -1, 0, 10}; 28 | REQUIRE(gos::to_string(arr) == "[1, 2, 3, -1, 0, 10, 0]"); 29 | } 30 | 31 | TEST_CASE("Float array basic stringification", "[Scalar]") { 32 | // It seems that only 6 digits of float are stored in the string stream 33 | std::array arr{1407654.807F, 34 | 2476523.4324F, 35 | -4342358.4987F, 36 | 34325.4234F, 37 | -23.5405F, 38 | 0.432454F, 39 | -42.76923F, 40 | 0.7650234F}; 41 | REQUIRE(gos::to_string(arr) == "[1.40765e+06, 2.47652e+06, -4.34236e+06, 34325.4," 42 | " -23.5405, 0.432454, -42.7692, 0.765023]"); 43 | } 44 | 45 | TEST_CASE("2D int array stringification", "[Complex]") { 46 | using Arr = std::array; 47 | std::array arr{{{1, 24234234, 3, -1, 0}, {10, 6000}, {777}}}; 48 | REQUIRE(gos::to_string(arr) == "{[1, 24234234, 3, -1, 0],\n" 49 | " [10, 6000, 0, 0, 0],\n" 50 | " [777, 0, 0, 0, 0]}"); 51 | } 52 | 53 | TEST_CASE("3D int array stringification", "[Complex]") { 54 | using Arr = std::array; 55 | // clang-format off 56 | std::array, 2> arr = { 57 | {{{{1, 2, 3, 4, 5}, {6, 7, 8, 9, 10}, {11, 12, 13, 14, 15}}}, 58 | {{{16, 17, 18, 19, 20}, {21, 22, 23, 24, 25}, {26, 27, 28, 29, 30}}}} 59 | }; 60 | // clang-format on 61 | REQUIRE(gos::to_string(arr) == "{{[1, 2, 3, 4, 5],\n" 62 | " [6, 7, 8, 9, 10],\n" 63 | " [11, 12, 13, 14, 15]},\n" 64 | " {[16, 17, 18, 19, 20],\n" 65 | " [21, 22, 23, 24, 25],\n" 66 | " [26, 27, 28, 29, 30]}}"); 67 | } 68 | } // namespace 69 | -------------------------------------------------------------------------------- /test/main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include "catch.hpp" 3 | -------------------------------------------------------------------------------- /test/pair.cpp: -------------------------------------------------------------------------------- 1 | #include "gos/goose.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include "catch.hpp" 7 | 8 | namespace { 9 | TEST_CASE("Empty pair of ints stringification", "[Empty]") { 10 | std::pair pair; 11 | REQUIRE(gos::to_string(pair) == "[0, 0]"); 12 | } 13 | 14 | TEST_CASE("Empty pair of vectors stringification", "[Empty]") { 15 | using Vec = std::vector; 16 | std::pair pair; 17 | REQUIRE(gos::to_string(pair) == "[[], []]"); 18 | } 19 | 20 | TEST_CASE("Pair of pair stringification", "[Scalar]") { 21 | using Pair = std::pair, std::pair>; 22 | Pair pair{{5425, -31659893}, {534.654745F, -0.653463657F}}; 23 | REQUIRE(gos::to_string(pair) == "[[5425, -31659893], [534.655, -0.653464]]"); 24 | } 25 | 26 | TEST_CASE("Pair of vectors stringification", "[Scalar]") { 27 | using Vec = std::vector; 28 | Vec vec{0, 10, 0, -1, 3, 2, 1}; 29 | std::pair pair(vec, vec); 30 | REQUIRE(gos::to_string(pair) == "[[0, 10, 0, -1, 3, 2, 1], [0, 10, 0, -1, 3, 2, 1]]"); 31 | } 32 | } // namespace 33 | -------------------------------------------------------------------------------- /test/string.cpp: -------------------------------------------------------------------------------- 1 | #include "gos/goose.hpp" 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace { 8 | TEST_CASE("Is empty string still empty", "[Empty]") { 9 | std::string str{}; 10 | REQUIRE(gos::to_string(str).empty()); 11 | } 12 | 13 | TEST_CASE("Is basic string still working", "[Scalar]") { 14 | std::string str{"something"}; 15 | REQUIRE(gos::to_string(str) == "something"); 16 | } 17 | } // namespace 18 | -------------------------------------------------------------------------------- /test/unordered_map.cpp: -------------------------------------------------------------------------------- 1 | #include "gos/goose.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace { 9 | TEST_CASE("Empty unordered map stringification", "[Empty]") { 10 | using HashMap = std::unordered_map; 11 | HashMap map{}; 12 | REQUIRE(gos::to_string(map) == "[]"); 13 | } 14 | 15 | TEST_CASE("Unordered map of string and int stringification", "[Complex]") { 16 | using HashMap = std::unordered_map; 17 | using Catch::Matchers::Contains; 18 | using Catch::Matchers::EndsWith; 19 | using Catch::Matchers::StartsWith; 20 | 21 | HashMap map{{"xD", 1}, {"smth", 2}, {":(((", 3}}; 22 | REQUIRE_THAT(gos::to_string(map), 23 | StartsWith("[[") && Contains("[xD, 1]") && Contains("[smth, 2]") && 24 | Contains("[:(((, 3]") && EndsWith("]]") && Contains(", ")); 25 | } 26 | } // namespace 27 | -------------------------------------------------------------------------------- /test/vector.cpp: -------------------------------------------------------------------------------- 1 | #include "gos/goose.hpp" 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace { 8 | TEST_CASE("Empty vector stringification", "[Empty]") { 9 | std::vector vec{}; 10 | REQUIRE(gos::to_string(vec) == "[]"); 11 | } 12 | 13 | TEST_CASE("Empty 2D vector stringification", "[Empty]") { 14 | std::vector> vec{}; 15 | REQUIRE(gos::to_string(vec) == "{}"); 16 | } 17 | 18 | TEST_CASE("Empty 4D vector stringification", "[Empty]") { 19 | std::vector>>> vec{}; 20 | REQUIRE(gos::to_string(vec) == "{}"); 21 | } 22 | 23 | TEST_CASE("Int vector stringification", "[Scalar]") { 24 | std::vector vec{0, 10, 0, -1, 3, 2, 1}; 25 | REQUIRE(gos::to_string(vec) == "[0, 10, 0, -1, 3, 2, 1]"); 26 | } 27 | 28 | TEST_CASE("Double vector stringification", "[Scalar]") { 29 | std::vector vec{0.3647438573, 30 | 10.5348534, 31 | -53450.63565, 32 | -1.54364367609, 33 | 343252355.767567, 34 | 2.654675478, 35 | 176575676.534}; 36 | REQUIRE(gos::to_string(vec) == "[0.364744, 10.5349, -53450.6, -1.54364, " 37 | "3.43252e+08, 2.65468, 1.76576e+08]"); 38 | } 39 | 40 | TEST_CASE("2D int vector stringification", "[Complex]") { 41 | using Vec2 = std::vector>; 42 | std::vector vec{0, 10, 0, -1, 3, 2, 1}; 43 | Vec2 vec2{vec, vec, vec, vec}; 44 | REQUIRE(gos::to_string(vec2) == "{[0, 10, 0, -1, 3, 2, 1],\n" 45 | " [0, 10, 0, -1, 3, 2, 1],\n" 46 | " [0, 10, 0, -1, 3, 2, 1],\n" 47 | " [0, 10, 0, -1, 3, 2, 1]}"); 48 | } 49 | 50 | TEST_CASE("3D int vector basic stringification", "[Complex]") { 51 | using Vec3 = std::vector>>; 52 | Vec3 vec{{{2, -2159, 69504, 664}, {543, 654}, {9098, 543}}, {}, {{11, 23}, {777}}}; 53 | REQUIRE(gos::to_string(vec) == "{{[2, -2159, 69504, 664],\n" 54 | " [543, 654],\n" 55 | " [9098, 543]},\n" 56 | " {},\n" 57 | " {[11, 23],\n" 58 | " [777]}}"); 59 | } 60 | } // namespace 61 | --------------------------------------------------------------------------------