Pure, functional script-like error handling in C ++

Here is an interesting task.

I wanted to create some boilerplate template that allows you to do clean pre-failure processing using a syntax similar to the perl approach x or die("reason").

I came up with this deleter:

struct do_exit {
    [[noreturn]]
    void operator()(void*) const noexcept {
        std::exit(code_);
    }
    int code_;
};

What can we use to control the “removal” of the temporary std::unique_ptr, which may indicate std::cerr:

struct exit_stream
{
    exit_stream(int code, std::ostream& os)
    : stream_(std::addressof(os), do_exit { code })
    {}

    std::ostream& as_stream() const { return *stream_; }
    operator bool() const { return bool(*stream_); }

    template<class T>
    friend auto operator<<(const exit_stream& es, T const& t) -> exit_stream const&
    {
        es.as_stream() << t;
        return es;
    }

    std::unique_ptr<std::ostream, do_exit> stream_;
};

Created with the following function:

exit_stream die(int code = 100)
{
    return exit_stream(code, std::cerr);
}

Now we can write the following program:

int main(int argc, char* argv[]) {

    // preconditions
    argc >= 2 or die(4) << "usage: myprog <inputfile>\n";

    auto input = std::ifstream(argv[1]);
    input or die(100) << "failed to open " << argv[1] << "\n";
}

, , exit_stream , (unique_ptr ). unique_ptr , . , , unique_ptr . , .

, :

auto input = std::ifstream(argv[1]]) or die() << "failed to open " << argv[1] << "\n";

, , input bool.

. ?

, , :

auto input = (std::ifstream(argv[1]]) or die() << "failed to open " << argv[1] << "\n").get();

:

#include <fstream>
#include <iostream>
#include <cstdlib>

struct do_exit {
    [[noreturn]]
    void operator()(void*) const noexcept {
        std::exit(code_);
    }
    int code_;
};

struct exit_stream
{
    exit_stream(int code, std::ostream& os)
    : stream_(std::addressof(os), do_exit { code })
    {}

    std::ostream& as_stream() const { return *stream_; }
    operator bool() const { return bool(*stream_); }

    template<class T>
    friend auto operator<<(const exit_stream& es, T const& t) -> exit_stream const&
    {
        es.as_stream() << t;
        return es;
    }

    std::unique_ptr<std::ostream, do_exit> stream_;
};

exit_stream die(int code = 100)
{
    return exit_stream(code, std::cerr);
}

int main(int argc, char* argv[]) {

    // preconditions
    argc >= 2 or die(4) << "usage: myprog <inputfile>\n";

    auto input = std::ifstream(argv[1]);
    input or die(100) << "failed to open " << argv[1];
}
+4
2

, . - , - , .

/ !

#include <fstream>
#include <iostream>
#include <utility>
#include <iomanip>

namespace detail {
    struct tuple_emitter {
        tuple_emitter(std::ostream &os) : os(os) {}

        template<class...Refs>
        void operator()(std::tuple<Refs...> const &t) const {
            emit_impl(t, std::make_index_sequence<sizeof...(Refs)>());
        }

    private:

        template<class T>
        void emit_impl(T const &t) const {
            os << t;
        }

        // make sure we handle lazy manipulators
        void emit_impl(std::ios_base (* f)(std::ios_base &)) const
        {
            f(os);
        }

        template<class Tie, std::size_t...Is>
        void emit_impl(Tie const &refs, std::index_sequence<Is...>) const {
            using expand = int[];

            void(expand{0,
                        ((emit_impl(std::get<Is>(refs))), 0)...});
            os << std::endl;
        };


        std::ostream &os;
    };

    // a class that emits a number of references to std::cerr and then
    // exits    
    template<class...Refs>
    struct dier {
        dier(int code, std::tuple<Refs...> t) : code(code), emitter(std::cout), refs_(t) {}

        template<class...Others, class Ref>
        dier(dier<Others...> const &original, Ref const &r)
                : code(original.code), emitter(original.emitter), refs_(std::tuple_cat(original.refs_, std::tie(r))) {};

        void operator()() const {
            emitter(refs_);
            std::exit(code);
        }

        int code;
        tuple_emitter emitter;
        std::tuple<Refs&...> refs_;
    };

    template<class...Refs, class Ref>
    auto operator<<(dier<Refs...> original, Ref const &ref) {
        return dier<Refs..., const Ref &>(original, ref);
    };


    template<class T, class...Refs>
    T operator||(T &&t, dier<Refs...> death) {
        if (!t) {
            death();
        }
        return std::move(t);
    };

    template<class T, class...Refs>
    T const &operator||(T &t, dier<Refs...> death) {
        if (!t) {
            death();
        }
        return t;
    };
}

auto die(int code = 100) {
    return detail::dier<>(code, std::make_tuple());
}

int main(int argc, char *argv[]) {

    // preconditions
    argc >= 2 or die(4) << "usage: myprog <inputfile>\n";

    auto input = std::ifstream(argv[1]) or die(5) << "failed to open " << std::setfill('-') << std::setw(10) << argv[1] << " and here is a hex number " << std::hex << 40;

}

:

:

usage: myprog <inputfile>

"foo.txt":

failed to open ---foo.txt and here is a hex number 28

- :

#include <fstream>
#include <iostream>
#include <utility>
#include <iomanip>

namespace notstd {

    namespace detail {
        template<class F, class T, std::size_t...Is>
        void apply_impl(F&& f, T&& t, std::index_sequence<Is...>)
        {
            using expand = int[];
            void(expand{0,
                        ((f(std::get<Is>(t))), 0)...});
        };
    }

    template<class F, class...Ts>
    void apply(F&& f, std::tuple<Ts...> const& t)
    {
        detail::apply_impl(std::forward<F>(f), t, std::make_index_sequence<sizeof...(Ts)>());
    };

    struct apply_to_stream
    {
        apply_to_stream(std::ostream& os)
                : os(os)
        {}

        template<class T>
        std::ostream& operator()(T&& t) const
        {
            return os << t;
        };

        std::ostream& operator()(std::ios_base& (*f)(std::ios_base&)) const
        {
            f(os);
            return os;
        };

        std::ostream& os;
    };


}

namespace detail {

    template<class Tuple>
    struct dier {
        constexpr dier(int code, Tuple t) : code(code), refs_(std::move(t)) {}

        void operator()() const {
            notstd::apply(notstd::apply_to_stream(std::cout), refs_);
            std::cout << '\n';
            std::exit(code);
        }

        int code;
        Tuple refs_;
    };

    template<class Tie>
    constexpr auto make_dier(int code, Tie&& t) {
        return detail::dier<Tie>(code, std::forward<Tie>(t));
    }

    template<class Tuple, class Ref>
    constexpr auto operator<<(dier<Tuple> original, Ref&& ref) {
        return make_dier(original.code, std::tuple_cat(original.refs_, std::tie(ref)));
    }


    template<class T, class...Refs>
    T operator||(T &&t, dier<Refs...> death) {
        if (!t) {
            death();
        }
        return std::forward<T>(t);
    }

}

template<class Tie = std::tuple<>>
constexpr auto die(int code = 100, Tie&& t = {}) {
    return detail::make_dier(code, std::forward<Tie>(t));
}

int main(int argc, char *argv[]) {

    // preconditions
    argc >= 2 or die(4) << "usage: myprog <inputfile>\n";

    auto input = std::ifstream(argv[1]) or die(5) << "failed to open " << std::setfill('-') << std::setw(10) << argv[1] << " and here is a hex number " << std::hex << 40;
}
+2

@RichardHodges.

-, :

namespace notstd {
  // takes a function object
  // returns a function object that calls the first function object once for each argument
  template<class F>
  auto foreach( F&& f ) {
    return [f=std::forward<F>(f)](auto&&...args)mutable{
      using discard=int[];
      (void)discard{0,(void(
        f(decltype(args)(args))
      ),0)...};
    };
  }
  // weak version of std::apply:
  template<std::size_t...Is, class F, class Tuple>
  decltype(auto) apply( std::index_sequence<Is...>, F&& f, Tuple&& tup) {
    return std::forward<F>(f)( std::get<Is>(std::forward<Tuple>(tup))... );
  }
  template<class F, class Tuple>
  decltype(auto) apply(F&& f, Tuple&& tup) {
    auto indexes = std::make_index_sequence< std::tuple_size< std::remove_reference_t<Tuple> >{} >{};
    return apply(indexes, std::forward<F>(f), std::forward<Tuple>(tup) );
  }
}

. - :

namespace death {
  namespace details {
    template<class Tuple>
    struct dier;
  }

  template<class Tuple=std::tuple<>>
  details::dier<Tuple> die( int code, std::ostream& os = std::cerr, Tuple&& tuple={} ) {
    return {code, os, std::forward<Tuple>(tuple)};
  }

  namespace details {
    // a class that emits a number of references to std::cerr and then
    // exits  
    template<class Tuple>
    struct dier {
      void operator()() const {
        notstd::apply( notstd::foreach( [&](auto const&e){ os << e; } ), refs_ );
        os << std::endl;
        std::exit(code);
      }

      int code;
      std::ostream& os;
      Tuple refs_;
      template<class Ref>
      friend auto operator<<(dier&& original, Ref const &ref) {
        return die( original.code, original.os,  std::tuple_cat( std::move(original).refs_, std::tie(ref) ) );
      }
      template<class T>
      friend T operator||(T &&t, dier&& death) {
        if (!t) {
          death();
        }
        return std::forward<T>(t);
      }
    };
  }
}

auto die(int code = 100, std::ostream& os = std::cerr) {
  return death::die(code, os);
}

, .

.

, ... , .

+1

Source: https://habr.com/ru/post/1673069/


All Articles