{"id":15038054,"url":"https://github.com/dobiasd/functionalplus","last_synced_at":"2025-05-14T17:09:09.928Z","repository":{"id":38789714,"uuid":"46746797","full_name":"Dobiasd/FunctionalPlus","owner":"Dobiasd","description":"Functional Programming Library for C++. Write concise and readable C++ code.","archived":false,"fork":false,"pushed_at":"2025-05-03T12:58:11.000Z","size":2812,"stargazers_count":2189,"open_issues_count":2,"forks_count":174,"subscribers_count":73,"default_branch":"master","last_synced_at":"2025-05-14T17:08:23.856Z","etag":null,"topics":["algorithms","c-plus-plus","c-plus-plus-14","c-plus-plus-17","composition","cpp","cpp14","functional-programming","header-only","library","range","stl"],"latest_commit_sha":null,"homepage":"http://www.editgym.com/fplus-api-search/","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Dobiasd.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":"CITATION.cff","codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":["dobiasd"]}},"created_at":"2015-11-23T20:41:52.000Z","updated_at":"2025-05-13T06:18:11.000Z","dependencies_parsed_at":"2022-07-18T08:14:15.616Z","dependency_job_id":"d7ff1ad5-d922-42b7-9510-799bf58cc32f","html_url":"https://github.com/Dobiasd/FunctionalPlus","commit_stats":{"total_commits":1582,"total_committers":37,"mean_commits":42.75675675675676,"dds":0.6422250316055627,"last_synced_commit":"1345a86bea6f70a7d0dfb79e4c4036f081a4694f"},"previous_names":[],"tags_count":27,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dobiasd%2FFunctionalPlus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dobiasd%2FFunctionalPlus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dobiasd%2FFunctionalPlus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dobiasd%2FFunctionalPlus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Dobiasd","download_url":"https://codeload.github.com/Dobiasd/FunctionalPlus/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254190395,"owners_count":22029632,"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":["algorithms","c-plus-plus","c-plus-plus-14","c-plus-plus-17","composition","cpp","cpp14","functional-programming","header-only","library","range","stl"],"created_at":"2024-09-24T20:36:55.410Z","updated_at":"2025-05-14T17:09:09.907Z","avatar_url":"https://github.com/Dobiasd.png","language":"C++","readme":"![logo](logo/fplus.png)\n\n[![CI](https://github.com/Dobiasd/FunctionalPlus/workflows/ci/badge.svg)](https://github.com/Dobiasd/FunctionalPlus/actions)\n[![(License Boost 1.0)](https://img.shields.io/badge/license-boost%201.0-blue.svg)][license]\n\n[license]: http://www.boost.org/LICENSE_1_0.txt\n\n\nFunctionalPlus\n==============\n**helps you write concise and readable C++ code.**\n\nTable of contents\n-----------------\n  * [Introduction](#introduction)\n  * [Usage examples](#usage-examples)\n  * [Type deduction and useful error messages](#type-deduction-and-useful-error-messages)\n  * [Tutorial](#tutorial)\n  * [Forward application and composition](#forward-application-and-composition)\n  * [Finding the functions you need](#finding-the-functions-you-need)\n  * [Performance](#performance)\n  * [Comparison with range-v3](#comparison-with-range-v3)\n  * [Requirements and Installation](#requirements-and-installation)\n\n\nIntroduction\n------------\nGreat code should mostly be self-documenting, but while using C++ in reality you can find yourself dealing with low-level stuff like iterators or hand-written loops that distract from the actual essence of your code.\n\n**FunctionalPlus is a small header-only library** supporting you in reducing code noise and in dealing with only one single level of abstraction at a time. By increasing brevity and maintainability of your code it can improve productivity (and fun!) in the long run. It pursues these goals by providing pure and easy-to-use functions that free you from implementing commonly used flows of control over and over again.\n\nSay you have a list of numbers and are interested in the odd ones only.\n\n```c++\nbool is_odd_int(int x) { return x % 2 != 0; }\n\nint main()\n{\n    typedef vector\u003cint\u003e Ints;\n    Ints values = {24, 11, 65, 44, 80, 18, 73, 90, 69, 18};\n    // todo: get odd numbers from values ...\n}\n```\n\nThere are different possibilities to attain your goal. Some of them are:\n\n1. write a (range based) for loop\n ```c++\n     Ints odds;\n     for (int x : values)\n     {\n         if (is_odd_int(x))\n         {\n             odds.push_back(x);\n         }\n     }\n ```\n\n2. use `std::copy_if` from the STL\n ```c++\n     Ints odds;\n     std::copy_if(std::begin(values), std::end(values),\n             std::back_inserter(odds), is_odd_int);\n ```\n\n3. use `keep_if` from `FunctionalPlus`\n ```c++\n     auto odds = fplus::keep_if(is_odd_int, values);\n ```\n\nIf you think version 3 could be the one most pleasant to work with, you might like FunctionalPlus.\nAnd if you still think the hand-written for loop is easier to understand, also consider what would happen if the loop body (i.e. a corresponding lambda function in the call to `fplus::keep_if`) would be much longer. When reading `keep_if` you would still immediately know that `odds` can only contain elements that came from `values` and were selected by some, possibly complicated, predicate. In the for loop case you have no idea what is happening until you read the whole loop body. The loop version probably would need a comment at the top stating what the use of `keep_if` would tell at first glance.\n\n\nUsage examples\n--------------\nBelow are some short examples showing nice things you can do with functions and containers using FunctionalPlus.\n\n### The same old song\nYou can test the content of a container for various properties, e.g.\n```c++\n#include \u003cfplus/fplus.hpp\u003e\n#include \u003ciostream\u003e\n\nint main()\n{\n    std::list\u003cstd::string\u003e things = {\"same old\", \"same old\"};\n    if (fplus::all_the_same(things))\n        std::cout \u003c\u003c \"All things being equal.\" \u003c\u003c std::endl;\n}\n```\n\n### The I in our `team`\nThere also are some convenience functions for retrieving properties of containers. For example you can count the occurrences of a character in a string.\n```c++\n#include \u003cfplus/fplus.hpp\u003e\n#include \u003ciostream\u003e\n\nint main()\n{\n    std::string team = \"Our team is great. I love everybody I work with.\";\n    std::cout \u003c\u003c \"There actually are this many 'I's in team: \" \u003c\u003c\n        fplus::count(\"I\", fplus::split_words(false, team)) \u003c\u003c std::endl;\n}\n```\n\nOutput:\n\n```\nThere actually are this many 'I's in team: 2\n```\n\n### The cutest kitty\nFinding the highest rated element in a container is very simple compared to a hand-written version([1](https://gist.github.com/Dobiasd/a4e7aa9c25a3dd4c0522d75a71e2a867#file-cuteness-cpp-L28-L38), [2](https://gist.github.com/Dobiasd/ca9963ecafa786a18bd5990414fd9a59#file-cuteness2-cpp-L28-L31)).\n```c++\n#include \u003cfplus/fplus.hpp\u003e\n#include \u003ciostream\u003e\n\nstruct cat\n{\n    double cuteness() const\n    {\n        return softness_ * temperature_ * roundness_ * fur_amount_ - size_;\n    }\n    std::string name_;\n    double softness_;\n    double temperature_;\n    double size_;\n    double roundness_;\n    double fur_amount_;\n};\n\nvoid main()\n{\n    std::vector\u003ccat\u003e cats = {\n        {\"Tigger\",   5, 5, 5, 5, 5},\n        {\"Simba\",    2, 9, 9, 2, 7},\n        {\"Muffin\",   9, 4, 2, 8, 6},\n        {\"Garfield\", 6, 5, 7, 9, 5}};\n\n    auto cutest_cat = fplus::maximum_on(std::mem_fn(\u0026cat::cuteness), cats);\n\n    std::cout \u003c\u003c cutest_cat.name_ \u003c\u003c\n        \" is happy and sleepy. *purr* *purr* *purr*\" \u003c\u003c std::endl;\n}\n```\n\nOutput:\n\n```\nMuffin is happy and sleepy. *purr* *purr* *purr*\n```\n\n### Function composition, binding, and map creation\nLet's say you have the following function [given](https://gist.github.com/Dobiasd/17f5eeab2ba0ee6631394f149fc61ce2).\n```c++\nstd::list\u003cint\u003e collatz_seq(int x);\n```\n\nAnd you want to create an `std::map\u003cstd::uint64_t, std::string\u003e` containing string representations of the [Collatz sequences](https://en.wikipedia.org/wiki/Collatz_conjecture) for all numbers below 30. You can implement this nicely in a functional way too.\n\n```c++\n#include \u003cfplus/fplus.hpp\u003e\n#include \u003ciostream\u003e\n\n// std::list\u003cstd::uint64_t\u003e collatz_seq(std::uint64_t x) { ... }\n\nint main()\n{\n    typedef std::list\u003cint\u003e Ints;\n\n    // [1, 2, 3 ... 29]\n    auto xs = fplus::numbers\u003cInts\u003e(1, 30);\n\n    // A function that does [1, 2, 3, 4, 5] -\u003e \"[1 =\u003e 2 =\u003e 3 =\u003e 4 =\u003e 5]\"\n    auto show_ints = fplus::bind_1st_of_2(fplus::show_cont_with\u003cInts\u003e, \" =\u003e \");\n\n    // A composed function that calculates a Collatz sequence and shows it.\n    auto show_collats_seq = fplus::compose(collatz_seq, show_ints);\n\n    // Associate the numbers with the string representation of their sequences.\n    auto collatz_dict = fplus::create_map_with(show_collats_seq, xs);\n\n    // Print some of the sequences.\n    std::cout \u003c\u003c collatz_dict[13] \u003c\u003c std::endl;\n    std::cout \u003c\u003c collatz_dict[17] \u003c\u003c std::endl;\n}\n```\n\nOutput:\n\n```\n[13 =\u003e 40 =\u003e 20 =\u003e 10 =\u003e 5 =\u003e 16 =\u003e 8 =\u003e 4 =\u003e 2 =\u003e 1]\n[17 =\u003e 52 =\u003e 26 =\u003e 13 =\u003e 40 =\u003e 20 =\u003e 10 =\u003e 5 =\u003e 16 =\u003e 8 =\u003e 4 =\u003e 2 =\u003e 1]\n```\n\nThe functions shown not only work with default STL containers like `std::vector`, `std::list`, `std::deque`, `std::string` etc. but also with custom containers providing a similar interface.\n\n\nType deduction and useful error messages\n----------------------------------------\n\nFunctionalPlus deduces types for you where possible. Let's take one line of code from the Collatz example:\n```c++\n    auto show_collats_seq = fplus::compose(collatz_seq, show_ints);\n```\n\n`collatz_seq` is a function taking an `uint64_t` and returning a `list\u003cuint64_t\u003e`. `show_ints` takes a `list\u003cuint64_t\u003e` and returns a `string`. By making use of `function_traits`, [written by kennyim](https://github.com/kennytm/utils/blob/master/traits.hpp), it is possible to automatically deduce the expression `fplus::compose(collatz_seq, show_ints)` as being a function taking an `uint64_t` and returning a `string`, so you do not have to manually provide type hints to the compiler.\n\nIf two functions whose \"connecting types\" do not match are passed in, an unambiguous error message describing the issue will be generated. FunctionalPlus uses compile time assertions to avoid the confusingly long error messages compilers generate when faced with type errors in function templates.\n\nChanging the way you program from \"writing your own loops and nested ifs\" to \"composing and using small functions\" will result in more errors at compile time but will pay out by having fewer errors at runtime. Also, more precise compile-time errors will reduce the time spent debugging.\n\nTutorial\n--------\n\nThe article \"[Functional programming in C++ with the FunctionalPlus library; today: HackerRank challenge Gemstones](https://github.com/Dobiasd/articles/blob/master/functional_programming_in_cpp_with_the_functionalplus_library_today_hackerrank_challange_gemstones.md)\" provides a smooth introduction into the library by showing how one could develop an elegant solution to a problem using the FunctionalPlus approach.\n\nAlso on Udemy there is a [course \"Functional Programming using C++\"](https://www.udemy.com/functional-programming-using-cpp/) that makes heavy use of FunctionalPlus to explain general functional concepts.\n\n\nForward application and composition\n-----------------------------------\n\nThe \"Gemstones\" tutorial above explains how one can apply functional thinking to arrive at the solution below for the following problem:\n\n\u003e Find the number of characters present in every line of an input text.\n\n```c++\nstd::string gemstone_count(const std::string\u0026 input)\n{\n    using namespace fplus;\n\n    typedef std::set\u003cstd::string::value_type\u003e characters;\n\n    const auto lines = split_lines(false, input); // false = no empty lines\n\n    const auto sets = transform(\n        convert_container\u003ccharacters, std::string\u003e,\n        lines);\n\n    // Build the intersection of all given character sets (one per line).\n    const auto gem_elements = fold_left_1(\n        set_intersection\u003ccharacters\u003e, sets);\n\n    return show(size_of_cont(gem_elements));\n}\n```\n\nBy using the functionality from `namespace fwd`, you can get along without temporary variables, and make it clear that the whole process is simply pushing the input through a chain of functions, similar to the pipe concept in the Unix command line.\n\n```c++\nstd::string gemstone_count_fwd_apply(const std::string\u0026 input)\n{\n    using namespace fplus;\n    typedef std::set\u003cstd::string::value_type\u003e characters;\n    return fwd::apply(\n        input\n        , fwd::split_lines(false)\n        , fwd::transform(convert_container\u003ccharacters, std::string\u003e)\n        , fwd::fold_left_1(set_intersection\u003ccharacters\u003e)\n        , fwd::size_of_cont()\n        , fwd::show()\n    );\n}\n```\n\nIn `fplus::fwd::` you find many `fplus::` functions again, but in a partially [curried](http://stackoverflow.com/a/36321/1866775) version, i.e. `fplus::foo : (a, b, c) -\u003e d` has its counterpart with `fplus::foo : (a, b) -\u003e (c -\u003e d)`. This makes the style above possible.\n\nAlternatively to the forward application version, you can also write [point-free](https://en.wikipedia.org/wiki/Tacit_programming) and define your function by composition:\n\n```c++\nusing namespace fplus;\ntypedef std::set\u003cstd::string::value_type\u003e characters;\n\nconst auto gemstone_count_fwd_compose = fwd::compose(\n    fwd::split_lines(false),\n    fwd::transform(convert_container\u003ccharacters, std::string\u003e),\n    fwd::fold_left_1(set_intersection\u003ccharacters\u003e),\n    fwd::size_of_cont(),\n    fwd::show()\n);\n```\n\nBy the way, in case you need the parameters of a binary function in reverse order, `namespace fplus::fwd::flip` also exists. `fplus::bar : (a, b) -\u003e c` does not only have its analog in `fplus::fwd::bar : a -\u003e b -\u003e c` but also in `fplus::fwd::flip::bar : b -\u003e a -\u003e c`.\n\n\nFinding the functions you need\n------------------------------\nIf you are looking for a specific FunctionalPlus function you do not know the name of yet, you can of course use the auto-complete feature of your IDE to browse the content of the `namespace fplus`. But the recommended way is to use the **[FunctionalPlus API search website](http://www.editgym.com/fplus-api-search/)**. You can quickly search by keywords or function type signatures with it. If you prefer, you can also [browse the source code using Sourcegraph](https://sourcegraph.com/github.com/Dobiasd/FunctionalPlus/-/tree/include/fplus).\n\n\nPerformance\n-----------\n\nThe basic functions are fast, thanks to C++'s concept of abstraction without overhead. Here are some measurements from the first example, taken on a standard desktop PC, compiled with GCC and the `O3` flag.\n```\n5000 random numbers, keep odd ones, 20000 consecutive runs accumulated\n----------------------------------------------------------------------\n\n| Hand-written for loop | std::copy_if | fplus::keep_if |\n|-----------------------|--------------|----------------|\n|               0.632 s |      0.641 s |        0.627 s |\n```\n\nSo the compiler seems to do a very good job optimizing and inlining everything to basically equal machine code performance-wise.\n\nThe more complex functions though sometimes could be written in a more optimized way. If you use FunctionalPlus in a performance-critical scenario and profiling shows you need a faster version of a function [please let me know](https://github.com/Dobiasd/FunctionalPlus/issues) or [even help improving FunctionalPlus](https://github.com/Dobiasd/FunctionalPlus/pulls).\n\nFunctionalPlus internally often can operate in-place if a given container is an r-value (e.g. in chained calls) and thus avoids many unnecessary allocations and copies. But this is not the case in all situations. However, thanks to working with a multi-paradigm language one easily can combine manually optimized imperative code with `fplus` functions. Luckily experience (aka. profiling) shows that in most cases the vast majority of code in an application is not relevant for overall performance and memory consumption. So initially focusing on developer productivity and readability of code is a good idea.\n\n\nComparison with range-v3\n------------------------\n\nFunctionalPlus and [range-v3](https://github.com/ericniebler/range-v3) ([basis](https://ericniebler.github.io/std/wg21/D4128.html) for `ranges` [in C++-20](https://en.cppreference.com/w/cpp/ranges)) do have things in common, as the following code snippet shows.\n\n```c++\nconst auto times_3 = [](int i){return 3 * i;};\nconst auto is_odd_int = [](int i){return i % 2 != 0;};\nconst auto as_string_length = [](int i){return std::to_string(i).size();};\n\n// FunctionalPlus\nusing namespace fplus;\nconst auto result_fplus = fwd::apply(\n    numbers(0, 15000000)\n    , fwd::transform(times_3)\n    , fwd::drop_if(is_odd_int)\n    , fwd::transform(as_string_length)\n    , fwd::sum());\n\n// range-v3\nconst auto result_range_v3 =\n    accumulate(\n        views::ints(0, ranges::unreachable)\n        | views::take(15000000)\n        | views::transform(times_3)\n        | views::remove_if(is_odd_int)\n        | views::transform(as_string_length), 0);\n```\n\nThere are some differences though. Range-v3 ranges are lazy, which means no intermediate memory is allocated during the single steps of a processing chain like the above.\nWhen using FunctionalPlus on the other hand you work with normal STL containers. Also [implementing a new function](https://github.com/Dobiasd/FunctionalPlus/blob/a17fc716d40a4370eed13f16e7d9105c4cc75e26/include/fplus/generate.hpp#L19) is simpler compared to [writing a new range adaptor](https://github.com/ericniebler/range-v3/blob/4cfcb59c3db1c279d72c64ccf15de3c724a0362d/include/range/v3/algorithm/generate.hpp#L32). Additionally FunctionalPlus provides much more functions out of the box and has the [API search website](http://www.editgym.com/fplus-api-search/). So the choice between the two libraries depends on your preferences and the project's needs.\n\n\nRequirements and Installation\n-----------------------------\n\nA **C++14**-compatible compiler is needed. Compilers from these versions on are fine:\n* GCC ( \u003e= 4.9 )\n* Clang ( \u003e= 3.7 with libc++ \u003e= 3.7 )\n* Visual Studio ( \u003e= 2015 )\n* XCode ( \u003e= 9 )\n\nGuides for different ways to install FunctionalPlus can be found in [INSTALL.md](INSTALL.md).\n\n\nDisclaimer\n----------\nThe functionality in this library initially grew due to my personal need for it while using C++ regularly. I try my best to make it error-free and as comfortable to use as I can. The API still might change in the future. If you have any suggestions, find errors, miss some functions, or want to give general feedback/criticism, I'd [love to hear from you](https://github.com/Dobiasd/FunctionalPlus/issues). Of course, [contributions](CONTRIBUTING.md) are also very welcome.\n\n\nLicense\n-------\nDistributed under the Boost Software License, Version 1.0.\n(See accompanying file [`LICENSE`](https://github.com/Dobiasd/FunctionalPlus/blob/master/LICENSE) or copy at\n[http://www.boost.org/LICENSE_1_0.txt](http://www.boost.org/LICENSE_1_0.txt))\n","funding_links":["https://github.com/sponsors/dobiasd"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdobiasd%2Ffunctionalplus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdobiasd%2Ffunctionalplus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdobiasd%2Ffunctionalplus/lists"}