Code Monkey home page Code Monkey logo

scelta's Introduction

scelta

C++17 zero-overhead syntactic sugar for variant and optional.

build stability license gratipay conan badge.cpp on-wandbox on-godbolt

Table of Contents

Overview

std::variant and std::optional were introduced to C++17's Standard Library. They are sum types that can greatly improve type safety and performance.

However, there are some problems with them:

  • The syntax of some common operations such as visitation is not as nice as it could be, and requires a significant amount of boilerplate.

  • Defining and using recursive variant or optional types is not trivial and requires a lot of boilerplate.

  • std::optional doesn't support visitation.

  • The interface of std::variant and std::optional is different from some other commonly used ADT implementations - interoperability requires significant boilerplate.

scelta aims to fix all the aformenetioned problems by providing zero-overhead syntactic sugar that:

  • Automatically detects and homogenizes all available variant and optional implementations, providing a single implementation-independent interface.

  • Provides "pattern matching"-like syntax for visitation and recursive visitation which works both for variant and optional.

  • Provides an intuitive placeholder-based recursive variant and optional type definition.

  • Provides monadic operations such as map and and_then for optional types, including infix syntax.

Implementation independent

scelta detects and works out-of-the-box with:

Other implementation can be easily adapted by providing specializations of the helper traits structs. PRs are welcome!

Curried visitation syntax

scelta provides curried, constexpr-friendly, and SFINAE-friendly visitation utilities both for variant and optional. The final user syntax resembles pattern matching. Recursive data structures are supported.

using shape = std::variant<circle, box>;

shape s0{circle{/*...*/}};
shape s1{box{/*...*/}};

// In place `match` visitation.
scelta::match([](circle, circle){ /* ... */ },
              [](circle, box)   { /* ... */ },
              [](box,    circle){ /* ... */ },
              [](box,    box)   { /* ... */ })(s0, s1);

The match function is intentionally curried in order to allow reuse of a particular visitor in a scope, even on different implementations of variant/optional.

using boost_optstr = boost::optional<std::string>;
using std_optstr = std::optional<std::string>;

// Curried `match` usage.
auto print = scelta::match([](std::string s)    { cout << s;       },
                           [](scelta::nullopt_t){ cout << "empty"; });

boost_optstr s0{/*...*/};
std_optstr s1{/*...*/};

// Implementation-independent visitation.
print(s0);
print(s1);

Recursive ADTs creation and visitation

Recursive variant and optional data structures can be easily created through the use of placeholders.

namespace impl
{
    namespace sr = scelta::recursive;

    // `placeholder` and `builder` can be used to define recursive
    // sum types.
    using _ = sr::placeholder;
    using builder = sr::builder<std::variant<int, std::vector<_>>>;

    // `type` evaluates to the final recursive data structure type.
    using type = sr::type<builder>;

    // `resolve` completely evaluates one of the alternatives.
    // (In this case, even the `Allocator` template parameter is
    // resolved!)
    using vector_type = sr::resolve<builder, std::vector<_>>;
}

using int_tree = impl::type;
using int_tree_vector = impl::vector_type;

After defining recursive structures, in place recursive visitation is also possible. scelta provides two ways of performing recursive visitation:

  • scelta::match(/* base cases */)(/* recursive cases */)(/* visitables */)

    This is an "homogeneous" match function that works for both non-recursive and recursive visitation. The first invocation always takes an arbitrary amount of base cases. If recursive cases are provided to the second invocation, then a third invocation with visitables is expected. Unless explicitly provided, the return type is deduced from the base cases.

    The base cases must have arity N, the recursive cases must have arity N + 1. N is the number of visitables that will be provided.

  • scelta::recursive::match</* return type */>(/* recursive cases */)(/* visitables */)

    This version always requires an explicit return type and an arbitrary amount of recursive cases with arity N + 1, where N is the number of visitables that will be provided.

int_tree t0{/*...*/};

scelta::match(
    // Base case.
    [](int x){ cout << x; }
)(
    // Recursive case.
    [](auto recurse, int_tree_vector v){ for(auto x : v) recurse(v); }
)(t0);

// ... or ...

scelta::recursive::match<return_type>(
    // Base case.
    [](auto, int x){ cout << x; },

    // Recursive case.
    [](auto recurse, int_tree_vector v){ for(auto x : v) recurse(v); }
)(t0);

Monadic optional operations

scelta provides various monadic operations that work on any supported optional type. Here's an example inspired by Simon Brand's "Functional exceptionless error-handling with optional and expected" article:

optional<image_view> crop_to_cat(image_view);
optional<image_view> add_bow_tie(image_view);
optional<image_view> make_eyes_sparkle(image_view);
image_view make_smaller(image_view);
image_view add_rainbow(image_view);

optional<image_view> get_cute_cat(image_view img)
{
    using namespace scelta::infix;
    return crop_to_cat(img)
         | and_then(add_bow_tie)
         | and_then(make_eyes_sparkle)
         | map(make_smaller)
         | map(add_rainbow);
}

Installation/usage

Quick start

scelta is an header-only library. It is sufficient to include it.

// main.cpp
#include <scelta.hpp>

int main() { return 0; }
g++ -std=c++1z main.cpp -Isome_path/scelta/include

Running tests and examples

Tests can be easily built and run using CMake.

git clone https://github.com/SuperV1234/scelta && cd scelta
./init-repository.sh # get `vrm_cmake` dependency
mkdir build && cd build

cmake ..
make check # build and run tests

make example_error_handling # error handling via pattern matching
make example_expression     # recursive expression evaluation
make example_optional_cat   # monadic optional operations

All tests currently pass on Arch Linux x64 with:

  • g++ (GCC) 8.0.0 20170514 (experimental)

  • clang version 5.0.0 (trunk 303617)

Integration with existing project

  1. Add this repository and SuperV1234/vrm_cmake as submodules of your project, in subfolders inside your_project/extlibs/:

    git submodule add   https://github.com/SuperV1234/vrm_cmake.git   your_project/extlibs/vrm_cmake
    git submodule add   https://github.com/SuperV1234/scelta.git      your_project/extlibs/scelta
  2. Include vrm_cmake in your project's CMakeLists.txt and look for the scelta extlib:

    # Include `vrm_cmake`:
    list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/extlibs/vrm_cmake/cmake/")
    include(vrm_cmake)
    
    # Find `scelta`:
    vrm_cmake_find_extlib(scelta)

Documentation

scelta::nonrecursive::visit

Executes non-recursive visitation.

  • Interface:

    template <typename Visitor, typename... Visitables>
    constexpr /*deduced*/ visit(Visitor&& visitor, Visitables&&... visitables)
        noexcept(/*deduced*/);
    • visitables... must all be the same type. (i.e. different implementations of variant/optional currently cannot be mixed together)

    • visitor must be invocable with all the alternatives of the passed visitables.

  • Examples:

    struct visitor
    {
        auto operator()(int) { return 0; }
        auto operator()(char){ return 1; }
    };
    
    variant<int, char> v0{'a'};
    assert(
        scelta::nonrecursive::visit(visitor{}, v0) == 1
    );
    struct visitor
    {
        auto operator()(int)              { return 0; }
        auto operator()(scelta::nullopt_t){ return 1; }
    };
    
    optional<int> o0{0};
    assert(
        scelta::nonrecursive::visit(visitor{}, o0) == 0
    );

scelta::nonrecursive::match

Executes non-recursive in-place visitation.

  • Interface:

    template <typename... FunctionObjects>
    constexpr /*deduced*/ match(FunctionObjects&&... functionObjects)
        noexcept(/*deduced*/)
    {
        return [o = overload(functionObjects...)](auto&&... visitables)
            noexcept(/*deduced*/)
            -> /*deduced*/
        {
            // ... perform visitation with `scelta::nonrecursive::visit` ...
        };
    };
    • Invoking match takes a number of functionObjects... and returns a new function which takes a number of visitables....

    • visitables... must all be the same type. (i.e. different implementations of variant/optional currently cannot be mixed together)

    • o must be invocable with all the alternatives of the passed visitables. (i.e. the overload of all functionObjects... must produce an exhaustive visitor)

  • Examples:

    variant<int, char> v0{'a'};
    assert(
        scelta::nonrecursive::match([](int) { return 0; }
                                    [](char){ return 1; })(v0) == 1
    );
    optional<int> o0{0};
    assert(
        scelta::nonrecursive::match([](int)              { return 0; }
                                    [](scelta::nullopt_t){ return 1; })(o0) == 1
    );

scelta::recursive::builder

Allows placeholder-based definition of recursive ADTs.

  • Interface:

    template <typename ADT>
    class builder;
    
    struct placeholder;
    
    template <typename Builder>
    using type = /* ... recursive ADT type wrapper ... */;
    
    template <typename Builder, typename T>
    using resolve = /* ... resolved ADT alternative ... */;
    • builder takes any ADT containing zero or more placeholder alternatives. (i.e. both optional and variant)

    • placeholder is replaced with the recursive ADT itself when using type or resolve.

    • type returns a wrapper around a fully-resolved recursive ADT.

    • resolve returns a fully-resolved alternative contained in ADT.

  • Examples:

    using _ = scelta::recursive::placeholder;
    using b = scelta::recursive::builder<variant<int, _*>>;
    
    using recursive_adt = scelta::recursive::type<b>;
    using ptr_alternative = scelta::recursive::resolve<b, _*>;
    
    recursive_adt v0{0};
    recursive_adt v1{&v0};

scelta::recursive::visit

Executes recursive visitation.

  • Interface:

    template <typename Return, typename Visitor, typename... Visitables>
    constexpr Return visit(Visitor&& visitor, Visitables&&... visitables)
        noexcept(false);
    • Similar to scelta::nonrecursive::visit, but requires an explicit return type and is not noexcept-friendly.

    • The operator() overloads of visitor... must take one extra generic argument to receive the recurse helper.

  • Examples:

    using _ = scelta::recursive::placeholder;
    using b = scelta::recursive::builder<variant<int, std::vector<_>>>;
    
    using recursive_adt = scelta::recursive::type<b>;
    using rvec = scelta::recursive::resolve<b, std::vector<_>>;
    
    struct visitor
    {
        auto operator()(auto,         int x)  { /* base case */ },
        auto operator()(auto recurse, rvec& v){ for(auto& x : v) recurse(x); }
    };
    
    recursive_adt v0{rvec{recursive_adt{0}, recursive_adt{1}}};
    scelta::recursive::visit(visitor{}, v0};

scelta::recursive::match

Executes recursive visitation.

  • Interface:

    template <typename Return, typename... FunctionObjects>
    constexpr auto match(FunctionObjects&&... functionObjects)
        noexcept(false)
    {
        return [o = overload(functionObjects...)](auto&&... visitables)
            noexcept(false)
            -> Return
        {
            // ... perform visitation with `scelta::recursive::visit` ...
        };
    };
    • Similar to scelta::nonrecursive::match, but requires an explicit return type and is not noexcept-friendly.

    • The passed functionObjects... must take one extra generic argument to receive the recurse helper.

  • Examples:

    using _ = scelta::recursive::placeholder;
    using b = scelta::recursive::builder<variant<int, std::vector<_>>>;
    
    using recursive_adt = scelta::recursive::type<b>;
    using rvec = scelta::recursive::resolve<b, std::vector<_>>;
    
    recursive_adt v0{rvec{recursive_adt{0}, recursive_adt{1}}};
    scelta::recursive::match(
        [](auto,         int x)  { /* base case */ },
        [](auto recurse, rvec& v){ for(auto& x : v) recurse(x); }
    )(v0);

scelta::match

Executes visitation (both non-recursive and recursive). Attempts to deduce the return type from the base cases, optionally supports user-provided explicit return type.

  • Interface:

    template <typename Return = impl::deduce_t, typename... BaseCases>
    constexpr auto match(BaseCases&&... baseCases)
    {
        return [bco = overload(adapt(baseCases)...)](auto... xs)
        {
            if constexpr(are_visitables<decltype(xs)...>())
            {
                // ... perform visitation with `scelta::nonrecursive::visit` ...
            }
            else
            {
                return [o = overload(bco, xs...)](auto&&... visitables)
                {
                    // ... perform visitation with `scelta::recursive::visit` ...
                };
            }
        };
    };
    • The first invocation of scelta::match takes one or more base cases. A base case is a function object with the same arity as the number of objects that will be visited.

    • The function returned by the first invocation takes either a number of recursive cases or a number of visitables.

      • Recursive cases are function objects with arity equal to the number of objects that will be visited plus one (the +1 is for the recurse argument).

      • Visitables are variants or optionals. If visitables are passed here, non-recursive visitation will be performed immediately.

    • If recursive cases were passed, the last returned function takes any number of visitables. Recursive visitation will then be performed immediately.

  • Examples:

    variant<int, char> v0{'a'};
    assert(
        scelta::match([](int) { return 0; }
                      [](char){ return 1; })(v0) == 1
    );
    using _ = scelta::recursive::placeholder;
    using b = scelta::recursive::builder<variant<int, std::vector<_>>>;
    
    using recursive_adt = scelta::recursive::type<b>;
    using rvec = scelta::recursive::resolve<b, std::vector<_>>;
    
    recursive_adt v0{rvec{recursive_adt{0}, recursive_adt{1}}};
    scelta::match(
        [](int x){ /* base case */ }
    )(
        [](auto recurse, rvec& v){ for(auto& x : v) recurse(x); }
    )(v0);

Monadic optional operations

scelta provides various monadic optional operations. They can be used in two different ways:

optional<int> o{/* ... */};

// Free function syntax:
scelta::map(o, [](int x){ return x + 1; });

// Infix syntax:
o | scelta::infix::map([](int x){ return x + 1; });

These are the available operations:

  • map_or_else(o, f_def, f)

    • Returns f(*o) if o is set, f_def() otherwise.
  • map_or(o, def, f)

    • Returns f(*o) if o is set, def otherwise.
  • map(o, f)

    • Returns optional{f(*o)} if o is set, an empty optional otherwise.
  • and_then(o, f)

    • Returns f(*o) if o is set, an empty optional otherwise.
  • and_(o, ob)

    • Returns ob if o is set, an empty ob otherwise.
  • or_else(o, f)

    • Returns o if o is set, f() otherwise.
  • or_(o, def)

    • Returns o if o is set, def otherwise.

The example file example/optional_cat.cpp shows usage of map and and_then using scelta::infix syntax.

Resources

scelta's People

Contributors

no-more-secrets avatar vittorioromeo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

scelta's Issues

Cannot `map` to a different type

Adding the following code to optional_monadic.cpp:

auto a = make();
auto f = [](auto x){return "oh no";};
scelta::map(a, f);

results in this error on GCC 7.2 (cut down for brevity):

In file included from /home/simon/scelta/include/scelta/utils.hpp:13:0,
                 from /home/simon/scelta/test/optional/../variant_test_utils.hpp:9,
                 from /home/simon/scelta/test/optional/optional_monadic.cpp:2:
/home/simon/scelta/include/scelta/./utils/optional_monadic.hpp:29:20: note: candidate: template<class Optional, class T, class F> constexpr decltype ((scelta::is_nullopt(o) ? forward<decltype (def)>(def) : forward<decltype (f)>(f)(scelta::impl::access_optional(forward<decltype (o)>(o))))) scelta::map_or(Optional&&, T&&, F&&)
     constexpr auto map_or(Optional&& o, T&& def, F&& f)
                    ^~~~~~
/home/simon/scelta/include/scelta/./utils/optional_monadic.hpp:29:20: note:   template argument deduction/substitution failed:
In file included from /home/simon/scelta/include/scelta/./support/./variant/./enable/../../../traits/adt/../dispatch.hpp:8:0,
                 from /home/simon/scelta/include/scelta/./support/./variant/./enable/../../../traits/adt/valid.hpp:7,
                 from /home/simon/scelta/include/scelta/./support/./variant/./enable/std.hpp:10,
                 from /home/simon/scelta/include/scelta/./support/./variant/std.hpp:13,
                 from /home/simon/scelta/include/scelta/./support/variant.hpp:7,
                 from /home/simon/scelta/include/scelta/support.hpp:7,
                 from /home/simon/scelta/test/optional/../variant_test_utils.hpp:8,
                 from /home/simon/scelta/test/optional/optional_monadic.cpp:2:
/home/simon/scelta/include/scelta/./utils/optional_monadic.hpp: In substitution of ‘template<class Optional, class T, class F> constexpr decltype ((scelta::is_nullopt(o) ? forward<decltype (def)>(def) : forward<decltype (f)>(f)(scelta::impl::access_optional(forward<decltype (o)>(o))))) scelta::map_or(Optional&&, T&&, F&&) [with Optional = boost::optional<int>&; T = boost::optional<int>; F = main()::<lambda(auto:15)> [with auto:15 = test::impl::maker_t<boost::optional<int> >]::<lambda(auto:16)>&]’:
/home/simon/scelta/include/scelta/./utils/optional_monadic.hpp:39:5:   required by substitution of ‘template<class Optional, class F> constexpr decltype (scelta::map_or(forward<decltype (o)>(o), std::decay_t<_Tp>{}, forward<decltype (f)>(f))) scelta::map(Optional&&, F&&) [with Optional = boost::optional<int>&; F = main()::<lambda(auto:15)> [with auto:15 = test::impl::maker_t<boost::optional<int> >]::<lambda(auto:16)>&]’
/home/simon/scelta/test/optional/optional_monadic.cpp:130:28:   required from ‘main()::<lambda(auto:15)> [with auto:15 = test::impl::maker_t<boost::optional<int> >]’
/home/simon/scelta/test/optional/../variant_test_utils.hpp:117:28:   required from ‘test::with_all_optional_implementations(TF&&)::<lambda(auto:14)> [with auto:14 = test::impl::unpack_alternatives<test::impl::alternatives_t<int> >::applier<boost::optional>; TAlternative = int; TF = main()::<lambda(auto:15)>]’
/home/simon/scelta/test/optional/../variant_test_utils.hpp:91:10:   required from ‘void test::instantiate_with_all_optional_implementations(TF&&) [with TestCase = test::impl::unpack_alternatives<test::impl::alternatives_t<int> >::applier; TF = test::with_all_optional_implementations(TF&&) [with TAlternative = int; TF = main()::<lambda(auto:15)>]::<lambda(auto:14)>]’
/home/simon/scelta/test/optional/../variant_test_utils.hpp:116:80:   required from ‘void test::with_all_optional_implementations(TF&&) [with TAlternative = int; TF = main()::<lambda(auto:15)>]’
/home/simon/scelta/test/optional/optional_monadic.cpp:134:10:   required from here
/home/simon/scelta/include/scelta/./utils/optional_monadic.hpp:31:23: error: operands to ?: have different types ‘boost::optional<int>’ and ‘const char*’
         is_nullopt(o) ? FWD(def)
         ~~~~~~~~~~~~~~^~~~~~~~~~
                       : FWD(f)(impl::access_optional(FWD(o)))

Expected behaviour: Compile and run

and_then has a similar issue.

A fix would be to get the return type of calling f with the value in the optional and explicitly construct the correct type on both branches of the conditional.

can not compile with visual studio 2017

hi:
Can not compile with visual studio 2017 with /std:c++17 command.

include\scelta\recursive\builder.hpp(39): error C2061: syntax error : identifier“resolved_type”

would you please get me out ! thank you very much!

Generic lambdas and const-qualified mem funcs break `map` and `and_then`

Adding this code to optional_monadic.cpp:

struct foo {
    foo non_const() { return {}; }
};

//...
with_all_optional_implementations<foo>( 
        [](auto make) {
                const auto a = make();
                auto f = [](auto &&x) { return x.non_const(); };
                scelta::map(a, f);
         }
);

results in this compiler error on GCC 7.2:

/home/simon/scelta/test/optional/optional_monadic.cpp: In instantiation of ‘main()::<lambda(auto:16)>::<lambda(auto:17&&)> [with auto:17 = const foo&; auto:16 = test::impl::maker_t<std::optional<foo> >]’:
/home/simon/scelta/include/scelta/./utils/optional_monadic.hpp:30:5:   required by substitution of ‘template<class Optional, class T, class F> constexpr decltype ((scelta::is_nullopt(o) ? forward<decltype (def)>(def) : forward<decltype (f)>(f)(scelta::impl::access_optional(forward<decltype (o)>(o))))) scelta::map_or(Optional&&, T&&, F&&) [with Optional = const std::optional<foo>&; T = std::optional<foo>; F = main()::<lambda(auto:16)> [with auto:16 = test::impl::maker_t<std::optional<foo> >]::<lambda(auto:17&&)>&]’
/home/simon/scelta/include/scelta/./utils/optional_monadic.hpp:39:5:   required by substitution of ‘template<class Optional, class F> constexpr decltype (scelta::map_or(forward<decltype (o)>(o), std::decay_t<_Tp>{}, forward<decltype (f)>(f))) scelta::map(Optional&&, F&&) [with Optional = const std::optional<foo>&; F = main()::<lambda(auto:16)> [with auto:16 = test::impl::maker_t<std::optional<foo> >]::<lambda(auto:17&&)>&]’
/home/simon/scelta/test/optional/optional_monadic.cpp:139:28:   required from ‘main()::<lambda(auto:16)> [with auto:16 = test::impl::maker_t<std::optional<foo> >]’
/home/simon/scelta/test/optional/../variant_test_utils.hpp:117:28:   required from ‘test::with_all_optional_implementations(TF&&)::<lambda(auto:14)> [with auto:14 = test::impl::unpack_alternatives<test::impl::alternatives_t<foo> >::applier<std::optional>; TAlternative = foo; TF = main()::<lambda(auto:16)>]’
/home/simon/scelta/test/optional/../variant_test_utils.hpp:87:10:   required from ‘void test::instantiate_with_all_optional_implementations(TF&&) [with TestCase = test::impl::unpack_alternatives<test::impl::alternatives_t<foo> >::applier; TF = test::with_all_optional_implementations(TF&&) [with TAlternative = foo; TF = main()::<lambda(auto:16)>]::<lambda(auto:14)>]’
/home/simon/scelta/test/optional/../variant_test_utils.hpp:116:80:   required from ‘void test::with_all_optional_implementations(TF&&) [with TAlternative = foo; TF = main()::<lambda(auto:16)>]’
/home/simon/scelta/test/optional/optional_monadic.cpp:141:11:   required from here
/home/simon/scelta/test/optional/optional_monadic.cpp:138:63: error: passing ‘const foo’ as ‘this’ argument discards qualifiers [-fpermissive]
                 auto f = [](auto &&x) { return x.non_const(); };
                                                               ^
/home/simon/scelta/test/optional/optional_monadic.cpp:7:9: note:   in call to ‘foo foo::non_const()’
     foo non_const() { return {}; }
         ^~~~~~~~~
/home/simon/scelta/test/optional/optional_monadic.cpp: In instantiation of ‘main()::<lambda(auto:16)>::<lambda(auto:17&&)> [with auto:17 = const foo&; auto:16 = test::impl::maker_t<boost::optional<foo> >]’:
/home/simon/scelta/include/scelta/./utils/optional_monadic.hpp:30:5:   required by substitution of ‘template<class Optional, class T, class F> constexpr decltype ((scelta::is_nullopt(o) ? forward<decltype (def)>(def) : forward<decltype (f)>(f)(scelta::impl::access_optional(forward<decltype (o)>(o))))) scelta::map_or(Optional&&, T&&, F&&) [with Optional = const boost::optional<foo>&; T = boost::optional<foo>; F = main()::<lambda(auto:16)> [with auto:16 = test::impl::maker_t<boost::optional<foo> >]::<lambda(auto:17&&)>&]’
/home/simon/scelta/include/scelta/./utils/optional_monadic.hpp:39:5:   required by substitution of ‘template<class Optional, class F> constexpr decltype (scelta::map_or(forward<decltype (o)>(o), std::decay_t<_Tp>{}, forward<decltype (f)>(f))) scelta::map(Optional&&, F&&) [with Optional = const boost::optional<foo>&; F = main()::<lambda(auto:16)> [with auto:16 = test::impl::maker_t<boost::optional<foo> >]::<lambda(auto:17&&)>&]’
/home/simon/scelta/test/optional/optional_monadic.cpp:139:28:   required from ‘main()::<lambda(auto:16)> [with auto:16 = test::impl::maker_t<boost::optional<foo> >]’
/home/simon/scelta/test/optional/../variant_test_utils.hpp:117:28:   required from ‘test::with_all_optional_implementations(TF&&)::<lambda(auto:14)> [with auto:14 = test::impl::unpack_alternatives<test::impl::alternatives_t<foo> >::applier<boost::optional>; TAlternative = foo; TF = main()::<lambda(auto:16)>]’
/home/simon/scelta/test/optional/../variant_test_utils.hpp:91:10:   required from ‘void test::instantiate_with_all_optional_implementations(TF&&) [with TestCase = test::impl::unpack_alternatives<test::impl::alternatives_t<foo> >::applier; TF = test::with_all_optional_implementations(TF&&) [with TAlternative = foo; TF = main()::<lambda(auto:16)>]::<lambda(auto:14)>]’
/home/simon/scelta/test/optional/../variant_test_utils.hpp:116:80:   required from ‘void test::with_all_optional_implementations(TF&&) [with TAlternative = foo; TF = main()::<lambda(auto:16)>]’
/home/simon/scelta/test/optional/optional_monadic.cpp:141:11:   required from here
/home/simon/scelta/test/optional/optional_monadic.cpp:138:63: error: passing ‘const foo’ as ‘this’ argument discards qualifiers [-fpermissive]
                 auto f = [](auto &&x) { return x.non_const(); };
                                                               ^
/home/simon/scelta/test/optional/optional_monadic.cpp:7:9: note:   in call to ‘foo foo::non_const()’
     foo non_const() { return {}; }
         ^~~~~~~~~
make[3]: *** [test/CMakeFiles/test.optional.optional_monadic.dir/build.make:63: test/CMakeFiles/test.optional.optional_monadic.dir/optional/optional_monadic.cpp.o] Error 1
make[2]: *** [CMakeFiles/Makefile2:339: test/CMakeFiles/test.optional.optional_monadic.dir/all] Error 2
make[1]: *** [CMakeFiles/Makefile2:75: CMakeFiles/check.dir/rule] Error 2
make: *** [Makefile:175: check] Error 2

Expected behaviour: compile and run

This is because the implementation of map uses trailing return types, even in C++14 mode, which will cause the body of the lambda to be instantiated for the const-qualified overload, giving a hard error (it won't SFINAE). and_then has a similar issue. This could be solved by using auto return type deduction when compiling in C++14 mode.

It took me ages to work out why this broke for my implementation 😆

expected support

It could be interesting for scelta to provide the same facilities for std/tl/nonstd/boost::expected

Monadic operations don't support member pointers

The monadic operations use f() syntax to call the function objects they're given. I think supporting member function/data pointers through std::invoke would be good so that users can write code like:

optional<string> s;
auto opt_size = map(s, &string::size);

Monadic operations don't support `void` returns

This is something of a niche feature, but I like it, so thought I'd suggest it anyway 😄

If you support mapping functions which return void then you can compose functions which have side effects:

optional<int> o = maybe_get_int();
o  | and_then(do_thing)
   | map(log); //only happens if both maybe_get_int() and do_thing returned values

I do this in my library by translating void returns to tl::monostate. Since you're using C++17, you could just use std::monostate.

Resolve recursive type by index

namespace sr = scelta::recursive;

using _ = sr::placeholder;
using builder = sr::builder<std::variant<int, std::vector<_>>>;
using type = sr::type<builder>;

Currently, to resolve the vector, the user has to say:

using vector_type = sr::resolve<builder, std::vector<_>>;

It would probably be nicer to say:

using vector_type = sr::resolve_nth<builder, 1>;

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.