Code Monkey home page Code Monkey logo

leaf's Introduction

LEAF

A lightweight error handling library for C++11.

Documentation

https://boostorg.github.io/leaf/

Features

  • Portable single-header format, no dependencies.
  • Tiny code size when configured for embedded development.
  • No dynamic memory allocations, even with very large payloads.
  • Deterministic unbiased efficiency on the "happy" path and the "sad" path.
  • Error objects are handled in constant time, independent of call stack depth.
  • Can be used with or without exception handling.

Support

Distribution

Besides GitHub, there are two other distribution channels:

  • LEAF is included in official Boost releases, starting with Boost 1.75.
  • For maximum portability, the library is also available in single-header format: simply download leaf.hpp (direct download link).

Copyright 2018-2023 Emil Dotchevski and Reverge Studios, Inc. Distributed under the http://www.boost.org/LICENSE_1_0.txt[Boost Software License, Version 1.0].

leaf's People

Contributors

akrzemi1 avatar eldiener avatar godmaycrying avatar indiosmo avatar kammce avatar pdimov avatar sdarwin avatar strager avatar tartanllama avatar vector-of-bool avatar zajo 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

leaf's Issues

Slightly modified exception_error_log example does not produce expected output

Hi,

I have slightly modified the following example https://github.com/boostorg/leaf/blob/master/example/error_log.cpp?ts=4 -> https://godbolt.org/z/48q9dnfTc
(f5() now calls f4() but discards its result and returns {})

However, even though the error should not reach the error logging handler, the error messages are printed. This is an example output:

Program stdout
Run # 0: Success!
Run # 1: Success!
Run # 2: Success!
Run # 3: Success!
Run # 4: Success!
Run # 5: Success!
Run # 6: Success!
Run # 7: Success!
Run # 8: Success!
Run # 9: Success!

Program stderr
Error! Log:
/app/example.cpp(64)
/app/example.cpp(73)
/app/example.cpp(82)
/app/example.cpp(91)
/app/example.cpp(100)
Error! Log:
/app/example.cpp(82)
/app/example.cpp(91)
/app/example.cpp(100)
Error! Log:
/app/example.cpp(73)
/app/example.cpp(82)
/app/example.cpp(91)
/app/example.cpp(100)
Error! Log:
/app/example.cpp(73)
/app/example.cpp(82)
/app/example.cpp(91)
/app/example.cpp(100)
Error! Log:
/app/example.cpp(64)
/app/example.cpp(73)
/app/example.cpp(82)
/app/example.cpp(91)
/app/example.cpp(100)
Error! Log:
/app/example.cpp(91)
/app/example.cpp(100)

Expected output: there should be no entry in stderr

Code in case godbolt not accessible

// Copyright 2018-2023 Emil Dotchevski and Reverge Studios, Inc.

// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

// This program demonstrates the use of leaf::on_error to print the path an
// error takes as it bubbles up the call stack. The printing code only runs if:
// - An error occurs, and
// - A handler that takes e_error_log argument is present. Otherwise none of the
//   error log machinery will be invoked by LEAF.

// This example is similar to error_trace, except the path the error takes is
// not captured, only printed.

#include <boost/leaf.hpp>
#include <iostream>
#include <cstdlib>

#define ENABLE_ERROR_LOG 1

namespace leaf = boost::leaf;

// The error log is activated only if an error handling scope provides a handler
// for e_error_log.
struct e_error_log
{
    struct rec
    {
        char const * file;
        int line;
        friend std::ostream & operator<<( std::ostream & os, rec const & x )
        {
            return os << x.file << '(' << x.line << ')';
        }
    };

    e_error_log()
    {
        std::cerr << "Error! Log:" << std::endl;
    }

    // Our e_error_log instance is stateless, used only as a target to
    // operator<<.
    template <class T>
    friend std::ostream & operator<<( e_error_log const &, T const & x )
    {
        return std::cerr << x << std::endl;
    }
};

// The ERROR_LOG macro is designed for use in functions that detect or forward
// errors up the call stack. If an error occurs, and if an error handling scope
// provides a handler for e_error_log, the supplied lambda is executed as the
// error bubbles up.
#define ERROR_LOG auto _log = leaf::on_error( []( e_error_log & log ) { log << e_error_log::rec{__FILE__, __LINE__}; } )

// Each function in the sequence below calls the previous function, and each
// function has failure_percent chance of failing. If a failure occurs, the
// ERROR_LOG macro will cause the path the error takes to be printed.
int const failure_percent = 25;

leaf::result<void> f1()
{
    ERROR_LOG;
    if( (std::rand()%100) > failure_percent )
        return { };
    else
        return leaf::new_error();
}

leaf::result<void> f2()
{
    ERROR_LOG;
    if( (std::rand()%100) > failure_percent )
        return f1();
    else
        return leaf::new_error();
}

leaf::result<void> f3()
{
    ERROR_LOG;
    if( (std::rand()%100) > failure_percent )
        return f2();
    else
        return leaf::new_error();
}

leaf::result<void> f4()
{
    ERROR_LOG;
    if( (std::rand()%100) > failure_percent )
        return f3();
    else
        return leaf::new_error();
}

leaf::result<void> f5()
{
    ERROR_LOG;
    f4();
    return {};
}

int main()
{
    for( int i=0; i!=10; ++i )
        leaf::try_handle_all(
            [&]() -> leaf::result<void>
            {
                std::cout << "Run # " << i << ": ";
                BOOST_LEAF_CHECK(f5());
                std::cout << "Success!" << std::endl;
                return { };
            },
#if ENABLE_ERROR_LOG // This single #if enables or disables the printing of the error log.
            []( e_error_log const & )
            {
            },
#endif
            []
            {
                std::cerr << "Error!" << std::endl;
            } );
    return 0;
}

////////////////////////////////////////

#ifdef BOOST_LEAF_NO_EXCEPTIONS

namespace boost
{
    [[noreturn]] void throw_exception( std::exception const & e )
    {
        std::cerr << "Terminating due to a C++ exception under BOOST_LEAF_NO_EXCEPTIONS: " << e.what();
        std::terminate();
    }

    struct source_location;
    [[noreturn]] void throw_exception( std::exception const & e, boost::source_location const & )
    {
        throw_exception(e);
    }
}

#endif

BOOST_LEAF_CHECK and extra semicolons

BOOST_LEAF_ASSIGN has no enclosing block, but BOOST_LEAF_CHECK does. This seems inconsistent and means that BOOST_LEAF_CHECK(mycoolthing()); results in an extra semicolon (and potentially a warning).

BOOST_LEAF_CFG_CAPTURE must be 1 for single-header build to compile

If BOOST_LEAF_CFG_CAPTURE is 0, then <memory> isn't included, but the code that uses std::shared_ptr is not stripped out by the preprocessor, causing a build failure.

This is from a single-header build with this at the top:
// Generated on 05/27/2022 from https://github.com/boostorg/leaf/tree/5e9a5b9.

match<...>.value() should return the error object, not the matched value

match<E, V...>::value() currently returns the matches value, which I wrote in my lambda, but it should return the error object which matched.

Consider https://godbolt.org/z/it5qjZ

We have

enum ErrorKind
{
    NoError,
    SomeError,
    OtherError
};

struct MyErrorType
{
    ErrorKind value;
    std::string Payload;
};

and

        ...
        [] (leaf::match<MyErrorType, ErrorKind::OtherError> Err) {
            std::cout << "Matched: " << Err.value() << std::endl;
            return -1;
        },
        ...

Currently Err.value() returns exactly what I wrote on the line above: ErrorKind::SomeError.

In the other case with leaf::match<MyErrorType, ErrorKind::SomeError, ErrorKind::MoreError> Err), Err.value() returns whichever of ErrorKind::SomeError or ErrorKind::MoreError actually matched.

In all cases, the actual error object is not accessible. As I am matching on the type of the error object, I am aware of the MyErrorType itself and it is probably public API and I can know the members of it. That means I can use Err.value().value to access what is currently available as Err.value but I can also Err.value().Payload to access the payload in the above case.

There is no loss of functionality by making the error instance available in error handlers. There is a gain in functionality.

This is true for any error type. I encountered it with std::error_code because I needed to access the message of the error_code. If match<E, V...>::value() returned the std::error_code itself, I could write:

        [] (boost::leaf::match<std::error_code, FileResult::FileNotFound> err)
        {
            std::error_code errc = err.value();
            std::cout << "File error: " << errc.message() << std::endl;
            return -1;
        },

As it is err.value() returns me FileResult::FileNotFound. It is not useful for the framework to tell me what I just told it.

No error_id loaded when catching an non-leaf::exception

This one took a while to track down, but I have narrowed it down to this small snippet. This is admittedly convoluted in appearance, but I was able to run into this behavior on my own with a much more spread-out code.

Here are two test cases that exhibit two related (but unexpected(?)) behaviors:

Error Object is Dropped:

auto fun = [] {
    return leaf::try_catch([]() -> std::pair<int, int> {
        auto augment = leaf::on_error(info2{1729});  // [1]
        leaf::try_catch(
            [] { 
                throw my_exception(12);
            },
            [](const my_exception& e) {
                leaf::current_error().load(info1{42});  // [2]
                throw;
            });
        // unreachable
    }, [](const my_exception& e, info1* v1, info2* v2) {
        // Return the pair of {info1, info2}
        return std::make_pair(v1 ? v1->value : -1, v2 ? v2->value : -1);
    });
};
auto pair = fun();
BOOST_TEST_EQ(pair.first, 42);
BOOST_TEST_EQ(pair.second, 1729);
pair = fun();
BOOST_TEST_EQ(pair.first, 42);
BOOST_TEST_EQ(pair.second, 1729);

In this case

  1. the bare throw my_exception does not initialize a new error_id in the current thread.
  2. The handler will now try to .load() into the current in-flight error at [2].
    • Since there is no new error_id in flight, it will attach info1 to whatever error_id just happened to be loaded in the current thread (Possibly just throwing the info away).
  3. The exception is then re-thrown with a bare throw;. Still, no new error_id is generated.
  4. The augment object's destructor at [1] will detect a new exception in-flight, but also detect that no new error_id has been created. It will then call new_error() (via error_monitor::check_id()) and attach an info2 to that error. The value of info1 is now inaccessible to the intended handler immediately below.

Additional quirk: If one moves the on_error object into the innermost throwing-lambda expression, then it's destructor will call new_error() (as expected!) before the exception is caught and this code will work.

Result differences:

auto fun = [](bool use_leaf_exception) {
    return leaf::try_catch([&]() -> std::pair<int, int> {
        auto augment = leaf::on_error(info2{1729}); // [1]
        leaf::try_catch(
            [&] { 
                if (use_leaf_exception) {
                    throw leaf::exception(my_exception(12));
                } else {
                    throw my_exception(12);
                }
            },
            [](const my_exception& e) {
                leaf::current_error().load(info1{42});  // [2]
                throw;
            });
        // unreachable
    }, [](const my_exception& e, info1* v1, info2* v2) {
        // Return the pair of {info1, info2}
        return std::make_pair(v1 ? v1->value : -1, v2 ? v2->value : -1);
    });
};
auto pair = fun(false);
BOOST_TEST_EQ(pair.first, 42);
BOOST_TEST_EQ(pair.second, 1729);
pair = fun(true);
BOOST_TEST_EQ(pair.first, 42);
BOOST_TEST_EQ(pair.second, 1729);

As a side effect of the prior quirk, the result will differ depending on whether leaf::exception() is called. In this case, if use_leaf_exception is true, then the correct result appears, otherwise it will fail

Dead PDF doc link

The PDF link on the docs page (right next to the correctly functioning Github button) appears to be dead.

When clicking it I get:

404 Not Found

File "/home/www/shared/archives/live/boost_1_75_0/libs/leaf/doc/html/leaf.pdf" not found.

Unable to find file.

Probably a typo somewhere?

Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/112.0

Modular Boost C++ Libraries Request

We are in the process of making B2 build changes to all of the B2 build files
to support "modular" consumption of the Boost Libraries by users. See this list
post for some details: https://lists.boost.org/Archives/boost/2024/01/255704.php

The process requires making a variety of changes to make each Boost library
independent of the super-project structure. But the changes do not remove the
super-project structure or the comprehensive Boost release. The changes make
solely make it possible, optionally, for users, like package manages, to easily
consume libraries individually.

Generally the changes include:

  • Adding a libroot/build.jam.
  • Porting any functionality from libroot/jamfile to libroot/build.jam.
  • Moving boost-install declaration from libroot/build/jamfile is applicable.
  • Adjusting other B2 build files in the library, like test/jamfile, as needed.
  • Possible changes to C++ source files to remove includes relative to the
    super-project boostroot location.

Some examples of such changes:

We are asking how you would like us to handle the changes. We would prefer if
you allow the owners of the Boost.org GitHub project to make changes to B2
build files, as needed, to accomplish the changes. But understand
that you may want to manage the proposed changes yourself.

We previously sent emails to all known maintainers to fill out a form with their
preference. We are contacting you in this issue as we have not gotten a response
to that email. You can see the ongoing responses for that form and the responses
to these issues here https://github.com/users/grafikrobot/projects/1/views/6

We are now asking if you can reply directly to this issue to indicate your
preference of handling the changes. Please supply a response to this question
and close the issue (so that we can verify you are a maintainer).

How would you like the build changes to be processed?

  1. Pull request, reviewed and merged by a BOOSTORG OWNER.
  2. Pull request, reviewed and merged by YOU.
  3. Other. (please specify details in the reply)

Also please indicate any special instructions you want us to consider. Or other
information you want us to be aware of.

Thanks you, René

Should LEAF provide allocating context for simple multi-thread error handling?

If I want to run user-provided function on my thread pool and return result, currently I have to ask user to provide error_handlers to use leaf::make_shared_context to allocate memory for error objects. This is not very convenient.

Other option is that I can force user to handle transportation of leaf::result between threads. In that case user will call make_shared_context, but that makes use of my library inconvenient.

In simplest case I just want to transport all errors between threads by allocating memory for error objects and allow user code to handle them. Maybe LEAF should provide such context?

Question: How to correctly rethrow/propagate?

I have a situation that looks something like this, which I am attempting to translate into LEAF:

try {
  MaybeThrowingOperation();
} catch (const my_error_type&) {
  if (/* conditional */) {
    throw;
  }
}

The conditional is not part of the exception type, but comes from the surrounding block, so it seems I can't use a matcher. I also considered simply returning leaf::new_error(e), but if some on_error data was attached, this would be discarded. I found leaf::current_error() which seems to do the trick. Is using that correct or is there a better way to "rethrow"?

boost::leaf::try_handle_some(
  [&] -> boost::leaf::result<void> {
    // Some operation that returns either a valid result<void>{} or a result with an error_id.
  },
  [&](const my_error_type&) -> boost::leaf::result<void> {
    if (/* conditional */) {
      return boost::leaf::current_error();
    } else {
      return {};
    }
  });

How to catch objects from upper levels of class hierarchy?

I am implementing some dummy class hierarchy like follows (SpecialAnimalHolderParent is actually needed as the classes on the left hand side are templated):

                                         AnimalHolder         SpecialAnimalHolderParent 
                                                  /             /
                                               SpecialAnimalHolder
                                              /                              
                          VerySpecialAnimalHolder

I would like to catch all instances of VerySpecialAnimalHolder and SpecialAnimalHolder by utlizing the following handler:
[&](const SpecialAnimalHolderParent& error) { std::cout << "SpecialAnimalHolderParent error object\n"; return 0; }

However, the handler is never entered when I create a new error of VerySpecialAnimalHolder type.

This is the godbolt implementation: https://godbolt.org/z/14cvqvMEh

Is there something special that needs to be implemented to enable such behavior?

explicit conversion constructor called implicitly in result<T> -> result<U> conversion

It looks like LEAF has a way to construct a result<U> from a result<T>. This is handy, but the conversion happens for explicit constructors, which I find surprising.

Here's an example:

#include <boost/leaf.hpp>
#include <string>
#include <vector>

boost::leaf::result<std::size_t> g() {
  return 42;
}

boost::leaf::result<std::vector<std::string>> f() {
  // Surprise! std::vector<std::string>::vector(std::size_t) is called.
  return g();
}

int main() {
  auto r = f();
  printf("vector contains %d strings\n", (int)r->size());
}

I expect the above example to fail to compile. std::vector<std::string>::vector(std::size_t) is an explicit constructor, so converting from a boost::leaf::result<std::size_t> to a boost::leaf::result<std::vector<std::string>> should fail.

If I use std::optional instead of boost::leaf::result everywhere in the example program, compilation fails, as expected:

test.cpp: In function ‘std::optional<std::vector<std::__cxx11::basic_string<char> > > f()’:
test.cpp:11:11: error: could not convert ‘g()’ from ‘optional<long unsigned int>’ to ‘optional<std::vector<std::__cxx11::basic_string<char> >>’
   11 |   return g();
      |          ~^~
      |           |
      |           optional<long unsigned int>

I like std::optional's behavior here. LEAF should not call an explicit constructor when converting result types.

[Feature] Support for C++20 coroutines

To use this library with C++20 coroutine the call in handle_error.hpp - try_handle_all or try_handle_some:

if( auto r = std::forward<TryBlock>(try_block)() )

Should check whether the returned time is an Awaitable and use

if( auto r = co_await std::forward<TryBlock>(try_block)() )

instead. Similarly for the error handling functions. This would allow users to write code among the lines of:

boost:leaf::try_handle_some(
    []() -> boost::asio::awaitable<boost:leaf::result<void>> {
        co_return boost::leaf::result<void>{};
    }, ...);

which is currently not possible. Maybe we could even achieve less verbosity. Ideally I would want to write co_return {}; just like before, but I think boost::leaf::result<> would need a constructor that takes an initializer_list then.

LEAF binaries are too large, due to including iostream/locale

Here's a link to the Slack thread where I first mentioned the issue.

The gist is that error.hpp includes sstream, which then includes iostream and locale. These two headers result in a binary increase of ~210 KB. My total binary size available is ~512 KB, so this makes using LEAF not realistically possible.

It looks like e_unexpected_info::add() uses stringstream. From our discussion, it was mentioned that it should be possible with the configuration macros to not include this, and thus not include sstream.

try_capture_all assert fails on catching an exception

The documentation for try_capture_all says "catching and capturing all exceptions and all communicated error objects in the returned leaf::result object", but instead in my case it just assert fails. Simple example:

#include <boost/leaf.hpp> 

int main() {
  boost::leaf::try_capture_all([]() { throw std::runtime_error("x"); });
}

Fails with:

a.out: /usr/include/boost/leaf/detail/optional.hpp:158: T &boost::leaf::leaf_detail::optional<boost::leaf::leaf_detail::dynamic_allocator>::value(int) & [T = boost::leaf::leaf_detail::dynamic_allocator]: Assertion `has_value(key) != 0' failed.
Aborted

If I compile with -DNDEBUG, it simply SIGSEGVs... Tried with system boost 1.85 and the leaf.hpp downloadable from here, same results: https://raw.githubusercontent.com/boostorg/leaf/gh-pages/leaf.hpp

Assertion failed: top_!=0 && *top_==this, when using Boost::coroutine

The following code triggers assertion: Assertion failed: top_!=0 && *top_==this, file boost/leaf/error.hpp, line 284. Both on development branch from 4th October 2020 and in version 0.2.1.

#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/leaf.hpp>

namespace bl = boost::leaf;
namespace asio = boost::asio;

int main() {
    asio::io_context io_context;
    asio::spawn(io_context, [&](auto &&yield) {
        bl::try_handle_some(
            [&]() -> bl::result<void> {
                asio::steady_timer timer(io_context);
                timer.expires_after(std::chrono::milliseconds(100));
                timer.async_wait(yield);
                return bl::new_error(42);
            },
            [&](const int &) {});
    });
    asio::spawn(io_context, [&](auto &&yield) {
        bl::try_handle_some(
            [&]() -> bl::result<void> {
                asio::steady_timer timer(io_context);
                timer.expires_after(std::chrono::seconds(1));
                timer.async_wait(yield);
                return {};
            },
            [](const int &) {});
    });
    io_context.run();
}

If the assertion doesn't trigger then you may want to increase the wait time of the first timer. The goal is to have the second try_handle_some function being called and stuck before the first one is called and has errored.

To compile the code you will need Boost and link with the needed libraries:

find_package(Boost 1.73 REQUIRED COMPONENTS coroutine)
target_link_libraries(main PRIVATE Boost::boost Boost::coroutine)

I have tested it with Boost 1.73 and 1.74, but I am sure previous versions can be used as well.

CTAD bug with GCC <11

I noticed very strange behavior from boost LEAF with GCC 9.3.0 if I use CTAD for boost::leaf::result.

Here is a small test program (try on Compiler Explorer):

#include <boost/leaf/result.hpp>
#include <cstdio>
#include <type_traits>

// Minimal repro based on boost::leaf::result:
template <class T>
class myresult {
  public:
    static int init_T_with_U( T && );
    template <class U> myresult( U && u, decltype(init_T_with_U(std::forward<U>(u))) * = nullptr );
};
template<> class myresult<void> { };

boost::leaf::result<void> g() { return {}; }
myresult<void> g2() { return {}; }

int main() {
  boost::leaf::result r = g();
  // This printf doesn't happen:
  std::printf("!!r = %d\n", (int)!!r);
  bool temp = !!r;
  std::printf("!!r (temp) = %d\n", (int)temp);

  // This static_assert fails:
  //static_assert(std::is_same_v<boost::leaf::result<void>, decltype(r)>);

  myresult r2 = g2();
  // This static_assert fails:
  //static_assert(std::is_same_v<myresult<void>, decltype(r2)>);
}

I think there is a GCC compiler bug. GCC somewhat silently miscompiles code. Output with GCC 9.3.0 on my machine:

!!r (temp) = 0

If we add the explicit template parameter (boost::leaf::result r = g(); -> boost::leaf::result<void> r = g();), the code behaves as expected. Output with GCC 9.3.0 on my machine:

!!r = 1
!!r (temp) = 1

With both variants, GCC 11.1.0 seems to work as expected on my machine:

!!r = 1
!!r (temp) = 1

Although this is probably a GCC bug, perhaps boost LEAF can work around the bug? Or document it?

try_handle_all forces a copy

        if( auto r = leaf_detail::try_catch_(
                ctx,
                [&]
                {
                    return std::forward<TryBlock>(try_block)();
                },
                std::forward<H>(h)...) )
            return r.value();

Shouldn't this be something like std::move(r).value()? Otherwise move-only types won't work. For example:

std::unique_ptr<Foo> p = leaf::try_handle_all(
  [&]() -> leaf::result<std::unique_ptr<Foo>> { return foo(); },
  [&](leaf::error_info const &) { return nullptr; }
);

new_error not throw error from error_handler

Hello! I've try to accept some error, and throw new, but in the next error handler i've not accept new error.

#include <iostream>
#include <ErrorCode/ErrorCode.h>

using RetType1 = ErrorCode::CustomError<int>; // boost::leaf::result<int>
using RetType2 = ErrorCode::CustomError<float>; // boost::leaf::result<float>

RetType1 test1(int a) {
    if (a == 1) {
        return ErrorCode::newError(ErrorCode::EC::E_GENERIC_UNEXPECTED); // boost::leaf::new_error(ErrorCode::EC::E_GENERIC_UNEXPECTED)
    }
    return 32;
}

RetType2 test2(int a) {
    return boost::leaf::try_handle_some(
            [a]() -> RetType2 {
                BOOST_LEAF_AUTO(val, test1(a));
                static_cast<void>(val);
                return 4.5;
            },
            [](boost::leaf::match<ErrorCode::EC, ErrorCode::EC::E_GENERIC_UNEXPECTED>) -> RetType2 {
                std::cerr << "catch err 1\n";
                return ErrorCode::newError(ErrorCode::EC::E_GENERIC_PARSE); // boost::leaf::new_error(ErrorCode::EC::E_GENERIC_PARSE)
            }
    );
}

void test3(int a) {
    return boost::leaf::try_handle_all(
            [a]() -> ErrorCode::Status {
                BOOST_LEAF_AUTO(val, test2(a));
                static_cast<void>(val);
                return {};
            },
            [](boost::leaf::match<ErrorCode::EC, ErrorCode::EC::E_GENERIC_PARSE>) {
                std::cerr << "yeah, correct!\n";
            },
            [](ErrorCode::EC e) {
                std::cerr << "error: " << int(e) << '\n';
            },
            []() {
                std::cerr << "unknown error\n";
            }
    );
}

int main() {
    test3(1);
    return 0;
}

Console output:

catch err 1
unknown error

Am I doing something wrong?

Why custom error object can not be handled when leaf::result is lay in the std::vector

Hi there,

I'd like to use std::vector to store a series of leaf::result, because both of a valid value or an error can be represent by leaf::result. Like, std::vector<boost::leaf::result<int>> result_vector.

Assuming I have an error object MyError:

struct MyError {
  MyError(int ecode, std::string emsg)
      : error_code(ecode), error_msg(std::move(emsg)) {}
  int error_code;
  std::string error_msg;
};

I'm trying to push_back a normal value and an error object into the vector, then iterate the vector. I expect MyError object can be handled, but it hits the unmatched branch.

  std::vector<boost::leaf::result<int>> result;
  result.emplace_back(123);
  result.emplace_back(boost::leaf::new_error(MyError(-1, "Invalid number")));

  boost::leaf::try_handle_all(
      [&result]() -> boost::leaf::result<int> {
        for (auto& e : result) {
          BOOST_LEAF_AUTO(val, e);
          std::cout << "Val: " << val << std::endl;
        }
        return 0;
      },
      [](const MyError& e) {
        std::cerr << "Found error: " << e.error_code << ", " << e.error_msg
                  << std::endl;
        return 1;
      },
      [](const boost::leaf::error_info& unmatched) {
        std::cerr << "Unmatched error: " << unmatched << std::endl;
        return 1;
      });

The output:

Val: 123
Unmatched error: leaf::error_info: Error ID = 1

Thanks,
Liang

exception() requires an rvalue-reference for its exception parameter.

The exception-aware overload of exception() look as follows (borrowing constraint syntax for conciseness):

template <typename Ex>
  requires std::is_base_of<std::exception, Ex>
exception(Ex&& ex) {
  // Throws a class derived from 'Ex' ...
}

This causes unexpected behavior if the exception object is an lvalue:

std::runtime_error exc{"Ouch"};
throw leaf::exception(exc);

Because is_base_of<std::exception, std::runtime_error&> is false, the other overload of exception() is selected, and a bare std::exception object will be returned and then thrown, instead of throwing the std::runtime_error exc. The runtime_error will instead be loaded as an exception value, which can sometimes be handled:

leaf::try_handle_some(
  [] {
    std::runtime_error exc{"Ouch"};
    throw leaf::exception(exc);
  },
  [] (leaf::catch_<std::runtime_error>) {}, // [1]
  [] (const std::runtime_error&) {}); // [2]

The [1] handler will not be invoked, but the [2] will.

Removing cvref qualifiers in the check causes exception to behave "as expected:"

template <typename Ex>
  requires std::is_base_of<std::exception, std::remove_cvref_t<Ex>>
exception(Ex&& ex) {
  // Throws a class derived from 'std::remove_cvref_t<Ex>' ...
}

I'm thinking a simple fix is possible, but want to confirm that this is actually an unintended behavior, and whether there might be other code locations that could also be affected.

Coroutines support

Thank you for building this library. I'm using it for embedded development and I'm very happy so far.

One question that has come up is about the support for coroutines. I know that leaf uses thread_local storage for its magic. What would happen if a coroutine is suspended and another takes its place, maybe even moving the first coroutine to another thread?

Lambdas don't work in macros pre-c++20

Up until c++20, the uses of decltype in the static asserts for BOOST_LEAF_ASSIGN and BOOST_LEAF_CHECK prevent patterns like BOOST_LEAF_AUTO(foo, create([&] { ... }())) (lambda expression in an unevaluated context). I thought this might be a concern since this library targets c++11. Maybe unchecked versions would make sense?

Anyway, thanks for a useful tool!

Failing to use leaf with Conan and CMake

Hi,

I used for a year stx library, and wanted to give boost::leaf a try.
As of writing the latest version of boost in Conan is 1.80.0.
As the conan package boost-leaf (1.81.0) was marked as deprecated recently, I cannot use leaf with Conan and CMake.
It seems CMake component leaf is inexistent.

In my conanfile.txt I have:
[requires] boost/1.80.0

In my CMakeLists.txt:
find_package(Boost REQUIRED COMPONENTS leaf)

It's failing with:
Conan: Component 'leaf' NOT found in package 'Boost'

Looking at the files in ~/.conan/, the library is there, but there is no reference to it in the conan/cmake files.

Am I doing something wrong?

FEATURE: Store all error objects when verbose_diagnostic_info is in try_handle_all handlers

Currently verbose_diagnostic_info is storing only strings received from applying operator<< to object, and only first time the type has encountered.
From the docs:

The message printed by operator<< includes the message printed by error_info, followed by information about error objects that were communicated to LEAF (to be associated with the error) for which there was no storage available in any active (these error objects were discarded by LEAF, because no handler needed them).

If I have some error context that is manipulating error object context using following .load overload:

f it is a function that takes a single argument of some type E &, that function is called with the object of type E currently associated with *this. If no such object exists, a default-initialized object is associated with *this and then passed to the function.

In that case verbose_diagnostic_info will print the string from deepest on_error in the stack, because to string conversion is happening on MyError creation.
auto load = leaf::on_error([](MyError& e) { e.append("Ctx1")} );

Since verbose_diagnostic_info is used for debug purposes, It looks like it may allocate storage for each error object on heap and apply operator<< to object only when user calls operator<< on verbose_diagnostic_info.

If this is not suitable for some reason, may be we can add heap_diagnostic_info that will allow to store error object dynamically, and inspect them.

This will be very useful, in application development, because you may never know what error object the underlying lib is using.

Another option is to add something like try_handle_all_heap, that receives only one handler as an argument that allows runtime inspection of error objects.

CMake Support

Ideally CMake users would be able to use the library like so:

find_package(leaf REQUIRED)
target_link_libraries(mytarget leaf::leaf) #or zajo::leaf if you prefer

Would you consider a PR to add this functionality? I'd then easily be able to also create a vcpkg package.

Few best practice questions

I hope the examples are self-explanatory :).

  1. Propagating errors outside of for loop: https://godbolt.org/z/ezcdGPKzs
  2. How to propagate AdditionalError from inner context (that does not have a suitable handler) to the outer context: https://godbolt.org/z/jT4GxbMEG
    I could not get this to work. The problem is return {}; in
[] (const CalcError& calc_error) -> leaf::result<OutputResult>
{
        std::cerr << "Calc Error!" << std::endl;
        return {};
}

Is it possible to check if any other errors are present in that scope?

  1. Bonus question: Would it be possible to extend on_error functionality to take lambda that is executed once an error occurs? Imagine a function that BOOST_ASSIGN several error reporting functions. If any of them reports an error some cleanup is to be executed. One could try to solve it by "on loading" RequestCleanup error that is then handled in the top level handler. The disadvantage is that the top level hander has to have access to the cleanup functionality.
    To go away with that, on load could take a a lambda with RequestCleanup reference parameter, but then the hander has to remember to allocate storage by specifying the corresponding handler.
    If that's not that clear I could also provide an example.

Add leaf to the Conan Center Index

Conan is one of the more popular C++ package managers out there with a ton of support. Having leaf on Conan will give it yet another distribution point and help with further adoption.

Documentation unclear on how to test the error-returning functions

Hi,

I'm trying to test some leaf-enabled code to verify that the error returned by the function is indeed the one that is expected.

I'm using Catch2 and I can't really figure out how to test for this. The leaf tutorial does not really go into any detail, while the examples and tests seem to only handle int-based return values, and in a very complicated way.

This is what I'm trying to do:

enum class ParserError {
	EmptyInput,
	UnsupportedFormat,
};

enum class ParserType {
	Json,
	Text,
};

leaf::result<ParserType> detect_input_type(const std::string& input)
{
	if (input.empty()) {
		return leaf::new_error(ParserError::EmptyInput);
	}
	// ...
	return ParserType::Json;
}

TEST_CASE("FormatDetection", "[parser]")
{
	// How to write this?
	REQUIRE(detect_input_type("").error() == ParserError::EmptyInput);  // compiler error
}

Thanks!

Better documentation of the core mechanism

It is possible to get more information how leaf is implemented? I'm particulary interested in the on_error and similar mechanisms which add information about the error context without affecting error-neutral and error-producing function implementations. Somehow leaf knows whether that extra information is useful based on the handler, but the handler can be defined in a separate translation unit.

I would like more information about the internal mechanism:

  • How exactly the implementation knows which info is useful and how does it preserve full type information?
  • Does the library use compiler-specific features?
  • Does the library use RTTI? There is a lot of info regarding exceptions but after reading whole documentation I haven't found a single mention of RTTI.

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.