{"id":13424694,"url":"https://github.com/eliaskosunen/scnlib","last_synced_at":"2025-05-14T18:07:01.322Z","repository":{"id":37431770,"uuid":"157940909","full_name":"eliaskosunen/scnlib","owner":"eliaskosunen","description":"scanf for modern C++","archived":false,"fork":false,"pushed_at":"2025-03-22T21:09:38.000Z","size":7299,"stargazers_count":1168,"open_issues_count":22,"forks_count":53,"subscribers_count":17,"default_branch":"master","last_synced_at":"2025-04-13T13:57:31.515Z","etag":null,"topics":["c-plus-plus","cpp","input","io","parsing","ranges","scanf"],"latest_commit_sha":null,"homepage":"https://scnlib.dev","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/eliaskosunen.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2018-11-17T01:48:40.000Z","updated_at":"2025-04-12T10:59:06.000Z","dependencies_parsed_at":"2023-12-12T00:54:10.709Z","dependency_job_id":"c293b429-ee7f-4b8d-9f41-8f20a2e0535b","html_url":"https://github.com/eliaskosunen/scnlib","commit_stats":null,"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eliaskosunen%2Fscnlib","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eliaskosunen%2Fscnlib/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eliaskosunen%2Fscnlib/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eliaskosunen%2Fscnlib/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/eliaskosunen","download_url":"https://codeload.github.com/eliaskosunen/scnlib/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254198515,"owners_count":22030966,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["c-plus-plus","cpp","input","io","parsing","ranges","scanf"],"created_at":"2024-07-31T00:00:57.977Z","updated_at":"2025-05-14T18:06:56.315Z","avatar_url":"https://github.com/eliaskosunen.png","language":"C++","funding_links":[],"categories":["Miscellaneous","C++","Text Handling"],"sub_categories":[],"readme":"# scnlib\n\n[![Linux builds](https://github.com/eliaskosunen/scnlib/actions/workflows/linux.yml/badge.svg)](https://github.com/eliaskosunen/scnlib/actions/workflows/linux.yml)\n[![macOS builds](https://github.com/eliaskosunen/scnlib/actions/workflows/macos.yml/badge.svg)](https://github.com/eliaskosunen/scnlib/actions/workflows/macos.yml)\n[![Windows builds](https://github.com/eliaskosunen/scnlib/actions/workflows/windows.yml/badge.svg)](https://github.com/eliaskosunen/scnlib/actions/workflows/windows.yml)\n[![Other architectures](https://github.com/eliaskosunen/scnlib/actions/workflows/arch.yml/badge.svg)](https://github.com/eliaskosunen/scnlib/actions/workflows/arch.yml)\n[![Code Coverage](https://codecov.io/gh/eliaskosunen/scnlib/graph/badge.svg?token=LyWrDluna1)](https://codecov.io/gh/eliaskosunen/scnlib)\n\n[![Latest Release](https://img.shields.io/github/v/release/eliaskosunen/scnlib?sort=semver\u0026display_name=tag)](https://github.com/eliaskosunen/scnlib/releases)\n[![License](https://img.shields.io/github/license/eliaskosunen/scnlib.svg)](https://github.com/eliaskosunen/scnlib/blob/master/LICENSE)\n[![C++ Standard](https://img.shields.io/badge/C%2B%2B-17%2F20%2F23-blue.svg)](https://img.shields.io/badge/C%2B%2B-17%2F20%2F23-blue.svg)\n[![Documentation](https://img.shields.io/badge/Documentation-scnlib.dev-blue)](https://scnlib.dev)\n\n```cpp\n#include \u003cscn/scan.h\u003e\n#include \u003cprint\u003e // for std::println (C++23)\n\nint main() {\n    // Read two integers from stdin\n    // with an accompanying message\n    if (auto result =\n            scn::prompt\u003cint, int\u003e(\"What are your two favorite numbers? \", \"{} {}\")) {\n        auto [a, b] = result-\u003evalues();\n        std::println(\"Oh, cool, {} and {}!\", a, b);\n    } else {\n        std::println(stderr, \"Error: {}\", result.error().msg());\n    }\n}\n```\n\nTry out in [Compiler Explorer](https://godbolt.org/z/oG71eorvE).\n\n## What is this?\n\n`scnlib` is a modern C++ library for replacing `scanf` and `std::istream`.\nThis library attempts to move us ever so much closer to replacing `iostream`s\nand C `stdio` altogether.\nIt's faster than `iostream` (see Benchmarks), and type-safe, unlike `scanf`.\nThink [{fmt}](https://github.com/fmtlib/fmt) or C++20 `std::format`, but in the\nother direction.\n\nThis library is the reference implementation of the ISO C++ standards proposal\n[P1729 \"Text Parsing\"](https://wg21.link/p1729).\n\n## Documentation\n\nThe documentation can be found online, at https://scnlib.dev.\n\nTo build the docs yourself, build the `scn_docs` target generated by CMake.\nThese targets are generated only if the variable `SCN_DOCS` is set in CMake\n(done automatically if scnlib is the root project).\nThe `scn_docs` target requires Doxygen, Python 3.8 or better, and the `pip3`\npackage `poxy`.\n\n## Examples\n\nSee more examples in the `examples/` folder.\n\n### Reading a `std::string`\n\n```cpp\n#include \u003cscn/scan.h\u003e\n#include \u003cprint\u003e\n\nint main() {\n    // Reading a std::string will read until the first whitespace character\n    if (auto result = scn::scan\u003cstd::string\u003e(\"Hello world!\", \"{}\")) {\n        // Will output \"Hello\":\n        // Access the read value with result-\u003evalue()\n        std::println(\"{}\", result-\u003evalue());\n        \n        // Will output \" world\":\n        // result-\u003erange() returns a subrange containing the unused input\n        // C++23 is required for the std::string_view range constructor used below\n        std::println(\"{}\", std::string_view{result-\u003erange()});\n    } else {\n        std::println(\"Couldn't parse a word: {}\", result.error().msg());\n    }\n}\n```\n\n### Reading multiple values\n\n```cpp\n#include \u003cscn/scan.h\u003e\n\nint main() {\n    auto input = std::string{\"123 456 foo\"};\n    \n    auto result = scn::scan\u003cint, int\u003e(input, \"{} {}\");\n    // result == true\n    // result-\u003erange(): \" foo\"\n    \n    // All read values can be accessed through a tuple with result-\u003evalues()\n    auto [a, b] = result-\u003evalues();\n    \n    // Read from the remaining input\n    // Could also use scn::ranges::subrange{result-\u003ebegin(), result-\u003eend()} as input\n    auto result2 = scn::scan\u003cstd::string\u003e(result-\u003erange(), \"{}\");\n    // result2 == true\n    // result2-\u003erange().empty() == true\n    // result2-\u003evalue() == \"foo\"\n}\n```\n\n### Reading from a fancier range\n\n```cpp\n#include \u003cscn/scan.h\u003e\n\n#include \u003cranges\u003e\n\nint main() {\n    auto result = scn::scan\u003cint\u003e(\"123\" | std::views::reverse, \"{}\");\n    // result == true\n    // result-\u003ebegin() is an iterator into a reverse_view\n    // result-\u003erange() is empty\n    // result-\u003evalue() == 321\n}\n```\n\n### Repeated reading\n\n```cpp\n#include \u003cscn/scan.h\u003e\n#include \u003cvector\u003e\n\nint main() {\n    std::vector\u003cint\u003e vec{};\n    auto input = scn::ranges::subrange{\"123 456 789\"sv};\n    \n    while (auto result = scn::scan\u003cint\u003e(input), \"{}\")) {\n        vec.push_back(result-\u003evalue());\n        input = result-\u003erange();\n    }\n}\n```\n\n## Features\n\n* Blazing fast parsing of values (see Benchmarks)\n* Modern C++ interface, featuring\n    * type safety (variadic templates, types not determined by the format\n      string)\n    * convenience (ranges)\n    * ergonomics (values returned from `scn::scan`, no output parameters)\n* `\"{python}\"`-like format string syntax\n    * Including compile-time format string checking\n* Minimal code size increase (in user code, see Benchmarks)\n* Usable without exceptions, RTTI, or `\u003ciostream\u003e`s\n    * Configurable through build flags\n    * Limited functionality if enabled\n* Supports, and requires Unicode (input is UTF-8, UTF-16, or UTF-32)\n* Highly portable\n    * Tested on multiple platforms, see CI\n    * Works on multiple architectures, tested on x86, x86-64, arm, aarch64,\n      riscv64, ppc64le, and riscv64\n\n## Installing\n\n`scnlib` uses CMake.\nIf your project already uses CMake, integration should be trivial, through\nwhatever means you like:\n`make install` + `find_package`, `FetchContent`, `git submodule` + `add_subdirectory`,\nor something else.\n\nThere are community-maintained packages available\non [Conan](https://conan.io/center/recipes/scnlib) and\non [vcpkg](https://github.com/microsoft/vcpkg/tree/master/ports/scnlib).\n\nThe `scnlib` CMake target is `scn::scn`\n\n```cmake\n# Target with which you'd like to use scnlib\nadd_executable(my_program ...)\ntarget_link_libraries(my_program scn::scn)\n```\n\nSee docs for usage without CMake.\n\n## Compiler support\n\nA C++17-compatible compiler is required. The following compilers are tested in\nCI:\n\n* GCC 7 and newer\n* Clang 8 and newer\n* Visual Studio 2019 and 2022\n\nIncluding the following environments:\n\n* 32-bit and 64-bit builds on Windows\n* libc++ on Linux\n* gcc on Alpine Linux\n* AppleClang and gcc on macOS 12 (Monterey) and 14 (Sonoma)\n* clang-cl with VS 2019 and 2022\n* MinGW and MSys2\n* GCC on armv6, armv7, aarch64, riscv64, s390x, and ppc64le\n* Visual Studio 2022, cross-compiling to arm64\n\n## Benchmarks\n\n### Run-time performance\n\nAll times below are in nanoseconds of CPU time.\nLower is better.\n\n#### Integer parsing (`int`)\n\n![Integer result, chart](benchmark/runtime/results/int.png)\n\n| Test                             | Test 1 `\"single\"` | Test 2 `\"repeated\"` | Test average |\n|:---------------------------------|------------------:|--------------------:|-------------:|\n| `scn::scan`                      |              23.8 |                30.4 |         27.1 |\n| `scn::scan_value`                |              20.5 |                27.4 |         24.0 |\n| `scn::scan_int`                  |              16.5 |                24.1 |         20.3 |\n| `scn::scan_int_exhaustive_valid` |              4.08 |                   - |         4.08 |\n| `std::stringstream`              |               117 |                53.9 |         85.5 |\n| `sscanf`                         |              71.3 |                 474 |        272.7 |\n| `strtol`                         |              16.3 |                23.8 |         20.1 |\n| `std::from_chars`                |              8.73 |                13.0 |         10.9 |\n| `fast_float::from_chars`         |              6.87 |                11.8 |         9.35 |\n\n#### Floating-point number parsing (`double`)\n\n![Float result, chart](benchmark/runtime/results/float.png)\n\n| Test                     | Test 1 `\"single\"` | Test 2 `\"repeated\"` | Test Average |\n|:-------------------------|------------------:|--------------------:|-------------:|\n| `scn::scan`              |              55.8 |                63.7 |         59.7 |\n| `scn::scan_value`        |              52.1 |                58.8 |         55.5 |\n| `std::stringstream`      |               294 |                 271 |          283 |\n| `sscanf`                 |               159 |                 704 |          432 |\n| `strtod`                 |              79.1 |                 153 |          116 |\n| `std::from_chars`        |              18.0 |                28.1 |         23.0 |\n| `fast_float::from_chars` |              20.6 |                27.8 |         24.2 |\n\n#### String \"word\" (whitespace-separated character sequence) parsing (`string` and `string_view`)\n\n![String result, chart](benchmark/runtime/results/string.png)\n\n| Test                           |      |\n|:-------------------------------|-----:|\n| `scn::scan\u003cstring\u003e`            | 24.5 |\n| `scn::scan\u003cstring_view\u003e`       | 22.2 |\n| `scn::scan_value\u003cstring\u003e`      | 23.1 |\n| `scn::scan_value\u003cstring_view\u003e` | 21.0 |\n| `std::stringstream`            |  134 |\n| `sscanf`                       | 58.4 |\n\n#### Conclusions\n\n* `scn::scan` is always faster than using `stringstream`s and `sscanf`\n* `std::from_chars`/`fast_float::from_chars` is faster than `scn::scan`, but it\n  supports fewer features\n* `strtod` is slower than `scn::scan`, and supports fewer features\n* `scn::scan_value` is slightly faster compared to `scn::scan`\n* `scn::scan_int` is faster than both `scn::scan` and `scn::scan_value`\n* `strtol` is ~on-par with `scn::scan_int`.\n* `scn::scan_int_exhaustive_valid` is blazing-fast.\n\n#### About\n\nAbove,\n\n* \"Test 1\" refers to scanning a single value from a string,\n  which only contains the text representation for that value.\n  The time used for creating any state needed for the scanner is included,\n  for example, constructing a `stringstream`. This test is called `\"single\"` in\n  the benchmark sources.\n* \"Test 2\" refers to the average time of scanning a value,\n  which contains multiple values in their text representations, separated by\n  spaces. The time used for creating any state needed for the scanner\n  is not included. This test is called `\"repeated\"` in the benchmark sources.\n* The string test is an exception: strings are read one after another from a\n  sample of Lorem Ipsum.\n\nThe difference between \"Test 1\" and \"Test 2\" is most pronounced when using\na `stringstream`, which is relatively expensive to construct,\nand seems to be adding around ~50ns of runtime.\nWith `sscanf`, it seems like using the `%n` specifier and skipping whitespace\nare really expensive (~400ns of runtime).\nWith `scn::scan` and `std::from_chars`, there's really no state to construct,\nand the results for \"Test 1\" and \"Test 2\" are thus quite similar.\n\nThese benchmarks were run on a Fedora 40 machine, running the Linux kernel version\n6.8.9, with an AMD Ryzen 7 5700X processor, and compiled with clang version 18.1.1,\nwith `-O3 -DNDEBUG -march=haswell` and LTO enabled.\nThese benchmarks were run on 2024-05-23 (commit 3fd830de).\n\nThe source code for these benchmarks can be found in the `benchmark` directory.\nYou can run these benchmarks yourself by enabling the CMake\nvariable `SCN_BENCHMARKS`.\nThis variable is `ON` by default, if `scnlib` is the root CMake project,\nand `OFF` otherwise.\n\n```sh\n$ cd build\n$ cmake -DSCN_BENCHMARKS=ON \\\n        -DCMAKE_BUILD_TYPE=Release -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON \\\n        -DSCN_USE_HASWELL_ARCH=ON ..\n$ cmake --build .\n# choose benchmarks to run in ./benchmark/runtime/*/*_bench\n$ ./benchmark/runtime/integer/scn_int_bench\n```\n\n### Executable size\n\nAll sizes below are in kibibytes (KiB), measuring the compiled executable.\n\"Stripped size\" shows the size of the executable after running `strip`.\nLower is better.\n\n#### Release build (`-O3 -DNDEBUG` + LTO)\n\n![Release result, chart](benchmark/binarysize/graph-release.png)\n\nSize of `scnlib` shared library (`.so`): 1.7M\n\n| Method         | Executable size | Stripped size |\n| :------------- | --------------: | ------------: |\n| empty          |             7.6 |           4.4 |\n| `std::scanf`   |            10.4 |           5.8 |\n| `std::istream` |            11.1 |           6.2 |\n| `scn::input`   |            11.2 |           6.4 |\n\n#### Minimized (MinSizeRel) build (`-Os -DNDEBUG` + LTO)\n\n![MinSizeRel result, chart](benchmark/binarysize/graph-minsizerel.png)\n\nSize of `scnlib` shared library (`.so`): 1.1M\n\n| Method         | Executable size | Stripped size |\n| :------------- | --------------: | ------------: |\n| empty          |             7.5 |           4.4 |\n| `std::scanf`   |            10.3 |           5.8 |\n| `std::istream` |            11.0 |           6.1 |\n| `scn::input`   |            12.4 |           6.6 |\n\n#### Debug build (`-g -O0`)\n\n![Debug result, chart](benchmark/binarysize/graph-debug.png)\n\nSize of `scnlib` shared library (`.so`): 20M\n\n| Method         | Executable size | Stripped size |\n| :------------- | --------------: | ------------: |\n| empty          |            18.4 |           5.2 |\n| `std::scanf`   |             429 |          11.8 |\n| `std::istream` |             438 |           9.4 |\n| `scn::input`   |            2234 |          51.3 |\n\n#### Conclusions\n\nWhen using optimized builds, depending on compiler flags, scnlib provides a\nbinary, the size of which is within ~5% of what would be produced with `scanf`\nor `\u003ciostream\u003e`s.\nIn a Debug-environment, scnlib is ~5x bigger when compared to `scanf`\nor `\u003ciostream\u003e`. After `strip`ping the binaries,\nthese differences largely go away, except in Debug builds.\n\n#### About\n\nIn these tests, 25 translation units are generated, in all of which values are\nread from `stdin` five times.\nThis is done to simulate a small project.\n`scnlib` is linked dynamically, to level the playing field with the standard\nlibrary, which is also dynamically linked.\n\nThe code was compiled on Fedora 40, with GCC 14.1.1.\nSee the directory `benchmark/binarysize` for the source code.\n\nYou can run these benchmarks yourself by enabling the CMake\nvariable `SCN_BENCHMARKS_BINARYSIZE`.\nThis variable is `ON` by default, if `scnlib` is the root CMake project,\nand `OFF` otherwise.\n\n```sh\n$ cd build\n# For Debug\n$ cmake -DCMAKE_BUILD_TYPE=Debug \\\n        -DSCN_BENCHMARKS_BINARYSIZE=ON \\\n        -DBUILD_SHARED_LIBS=ON ..\n# For Release and MinSizeRel,\n# add -DCMAKE_BUILD_TYPE=$BUILD_TYPE and\n# -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON\n\n$ cmake --build .\n$ ./benchmark/binarysize/run_binarysize_bench.py ./benchmark/binarysize $BUILD_TYPE\n```\n\n### Build time\n\n#### Build time\n\nTime is in seconds of CPU time (user time + sys/kernel time).\nLower is better.\n\n| Method       | Debug | Release |\n|:-------------|------:|--------:|\n| empty        |  0.05 |    0.05 |\n| `scanf`      |  0.22 |    0.20 |\n| `\u003ciostream\u003e` |  0.28 |    0.27 |\n| `scn::input` |  0.54 |    0.45 |\n\n#### Memory consumption\n\nMemory is in mebibytes (MiB) used while compiling.\nLower is better.\n\n| Method       | Debug | Release |\n|:-------------|------:|--------:|\n| empty        |  21.0 |    23.3 |\n| `scanf`      |  56.3 |    53.6 |\n| `\u003ciostream\u003e` |  67.8 |    65.0 |\n| `scn::input` |   102 |    91.0 |\n\n#### Conclusions\n\nCode using scnlib takes around 2x longer to compile compared to `\u003ciostream\u003e`,\nand also uses around 1.5x more memory.\nRelease builds seem to be slightly faster as compared to Debug builds.\n\n#### About\n\nThese tests measure the time it takes to compile a binary when using different\nlibraries.\nThe time taken to compile the library itself is not taken into account\n(the standard library is precompiled, anyway).\n\nThese tests were run on a Fedora 40 machine, with an AMD Ryzen 7 5700X\nprocessor, using GCC version 14.1.1.\nThe compiler flags used for a Debug build were `-g`, and `-O3 -DNDEBUG` for a\nRelease build.\n\nYou can run these benchmarks yourself by enabling the CMake\nvariable `SCN_BENCHMARKS_BUILDTIME`.\nThis variable is `ON` by default, if `scnlib` is the root CMake project,\nand `OFF` otherwise.\nFor these tests to work, `c++` must point to a GCC-compatible C++\ncompiler binary,\nand a somewhat POSIX-compatible `/usr/bin/time` must be available.\n\n```sh\n$ cd build\n$ cmake -DSCN_BENCMARKS_BUILDTIME=ON ..\n$ cmake --build .\n$ ./benchmark/buildtime/run-buildtime-tests.sh\n```\n\n## Acknowledgements\n\nThe contents of this library are heavily influenced by {fmt} and its derivative\nworks.  \nhttps://github.com/fmtlib/fmt\n\nThe design of this library is also inspired by the Python `parse` library:  \nhttps://github.com/r1chardj0n3s/parse\n\n### Third-party libraries\n\n*fast_float* for floating-point number parsing:  \nhttps://github.com/fastfloat/fast_float\n\n*NanoRange* for a minimal `\u003cranges\u003e` implementation:  \nhttps://github.com/tcbrindle/NanoRange\n\n## License\n\nscnlib is licensed under the Apache License, version 2.0.  \nCopyright (c) 2017 Elias Kosunen  \nSee LICENSE for further details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feliaskosunen%2Fscnlib","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feliaskosunen%2Fscnlib","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feliaskosunen%2Fscnlib/lists"}