Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Exceptional type safety

Exceptional type safety

Humouristic abuse of the C++ exception system for polymorphic return.
Presentation given at the distributed SwedenCPP Stockholm + London C++ UG meetup.

Björn Fahller

October 26, 2017
Tweet

More Decks by Björn Fahller

Other Decks in Programming

Transcript

  1. Exceptional Type Safety, C++ Stockholm 0x07, Björn Fahller @bjorn_fahller
    Exceptional Type Safety
    Björn Fahller
    E --> E "+" E
    | E "-" E
    | "-" E
    | E "*" E
    | E "/" E
    | "(" E ")"
    | v

    View Slide

  2. Exceptional Type Safety, C++ Stockholm 0x07, Björn Fahller @bjorn_fahller
    std::variant
    using v = std::variantstd::string,
    bool,
    std::list>;
    void func(v obj) {
    std::visit(overload(
    [](int) { /* int stuff */ },
    [](std::string) { /* string stuff */ },
    [](const auto&) { /* other stuff */ }),
    v);
    }

    View Slide

  3. Exceptional Type Safety, C++ Stockholm 0x07, Björn Fahller @bjorn_fahller
    std::variant
    using v = std::variantstd::string,
    bool,
    std::list>;
    void func(v obj) {
    std::visit(overload(
    [](int) { /* int stuff */ },
    [](std::string) { /* string stuff */ },
    [](const auto&) { /* other stuff */ }),
    v);
    }
    But what if we’re
    writing C++11 code?

    View Slide

  4. Exceptional Type Safety, C++ Stockholm 0x07, Björn Fahller @bjorn_fahller
    throw/catch
    try {
    func();
    }
    catch (A& a) { ... }
    catch (B& b) { ... }
    catch (...) { ... }

    View Slide

  5. Exceptional Type Safety, C++ Stockholm 0x07, Björn Fahller @bjorn_fahller
    throw/catch
    try {
    func();
    }
    catch (A& a) { ... }
    catch (B& b) { ... }
    catch (...) { ... }

    View Slide

  6. Exceptional Type Safety, C++ Stockholm 0x07, Björn Fahller @bjorn_fahller
    throw/catch
    try {
    func();
    }
    catch (A& a) { ... }
    catch (B& b) { ... }
    catch (...) { ... }
    So exceptions can give us
    the same desired behaviour
    as std::visit() does for
    std::variant<>

    View Slide

  7. Exceptional Type Safety, C++ Stockholm 0x07, Björn Fahller @bjorn_fahller
    Representing tokens
    Tokens for a very simple calculator.
    template
    struct C {};
    struct number { double value; };
    struct ident { std::string_view value; };
    struct eof {};
    struct remember {};
    struct forget {};

    View Slide

  8. Exceptional Type Safety, C++ Stockholm 0x07, Björn Fahller @bjorn_fahller
    Representing tokens
    Tokens for a very simple calculator.
    template
    struct C {};
    struct number { double value; };
    struct ident { std::string_view value; };
    struct eof {};
    struct remember {};
    struct forget {};
    struct other {};
    class lex
    {
    public:
    lex(std::string s);
    std::string next_token(); // throws token
    std::string peek(); // throws token
    void drop();
    private:
    ...
    };

    View Slide

  9. Exceptional Type Safety, C++ Stockholm 0x07, Björn Fahller @bjorn_fahller
    Representing tokens
    Tokens for a very simple calculator.
    template
    struct C {};
    struct number { double value; };
    struct ident { std::string_view value; };
    struct eof {};
    struct remember {};
    struct forget {};
    struct other {};
    class lex
    {
    public:
    lex(std::string s);
    std::string next_token(); // throws token
    std::string peek(); // throws token
    void drop();
    private:
    ...
    };
    std::string lex::scan_token()
    {
    ...
    switch (*iter)
    {
    case '(': ++iter;throw C{};
    case ')': ++iter;throw C{};
    case '/': ++iter;throw C{};
    case '*': ++iter;throw C{};
    case '=': ++iter;throw C{};
    case '+': ++iter;throw C{};
    case '-': ++iter;throw C{};
    case '0': case '1': case '2': case '3': case '4':
    case '5': case '6': case '7': case '8': case '9': case '.':
    return scan_number();
    }
    return "unknown";
    }

    View Slide

  10. Exceptional Type Safety, C++ Stockholm 0x07, Björn Fahller @bjorn_fahller
    Representing tokens
    Tokens for a very simple calculator.
    template
    struct C {};
    struct number { double value; };
    struct ident { std::string_view value; };
    struct eof {};
    struct remember {};
    struct forget {};
    struct other {};
    class lex
    {
    public:
    lex(std::string s);
    std::string next_token(); // throws token
    std::string peek(); // throws token
    void drop();
    private:
    ...
    };
    std::string lex::peek()
    {
    if (!lookahead)
    {
    try
    {
    return scan_token();
    }
    catch (...)
    {
    lookahead = std::current_exception();
    }
    }
    std::rethrow_exception(lookahead);
    }

    View Slide

  11. Exceptional Type Safety, C++ Stockholm 0x07, Björn Fahller @bjorn_fahller
    double calculator::parse(std::string s)
    {
    lexer.reset(new lex(std::move(s)));
    try { lexer->peek(); }
    catch (remember){ lexer->drop(); return parse_remember_var();}
    catch (forget) { lexer->drop(); return parse_forget_var();}
    catch (...) { return parse_expr();}
    try { lexer->next_token(); }
    catch (eof) { }
    catch (...) { throw "garbage after expr";}
    }
    double calculator::parse_expr()
    {
    auto v = parse_term();
    for (bool done = false; !done;) {
    try { lexer->peek(); }
    catch (C) { lexer->drop();v += parse_term(); }
    catch (C) { lexer->drop();v -= parse_term(); }
    catch (...) { done = true; }
    }
    return v;
    }
    double calculator::parse_term()
    {
    auto v = parse_factor();
    for (bool done = false; !done;) {
    try { lexer->peek(); }
    catch (C) { lexer->drop(); v /= parse_factor(); }
    catch (C) { lexer->drop(); v *= parse_factor(); }
    catch (...) { done = true; }
    }
    return v;
    }
    double calculator::parse_factor()
    {
    try { lexer->next_token(); }
    catch (ident var) { return lookup(var.value);}
    catch (number n) { return n.value;}
    catch (C) { return parse_term(); }
    catch (C) { return -parse_term(); }
    catch (C) { return parse_paren(); }
    catch (...) {}
    throw "unexpected";
    }
    double calculator::parse_paren()
    {
    auto v = parse_expr();
    try { lexer next_token(); }

    catch (C) { return v; }
    catch (...) { }
    throw "expected ')'";
    }
    double calculator::parse_forget_var()
    {
    try { lexer->next_token();}
    catch (const ident& i) { return drop_variable(i.value);}
    catch (...) { throw "expected variable name";}
    }
    double calculator::parse_remember_var()
    {
    std::string id;
    try { lexer->next_token(); }
    catch (ident i) { id = std::move(i.value);}
    catch (...) { throw "expected variable name";}
    try { lexer->next_token(); }
    catch (C) {}
    catch (...) { throw "expected =";}
    auto v = parse_expr();
    memory[id] = v;
    return v;
    }

    View Slide

  12. Exceptional Type Safety, C++ Stockholm 0x07, Björn Fahller @bjorn_fahller
    double calculator::parse(std::string s)
    {
    lexer.reset(new lex(std::move(s)));
    try { lexer->peek(); }
    catch (remember){ lexer->drop(); return parse_remember_var();}
    catch (forget) { lexer->drop(); return parse_forget_var();}
    catch (...) { return parse_expr();}
    try { lexer->next_token(); }
    catch (eof) { }
    catch (...) { throw "garbage after expr";}
    }
    double calculator::parse_expr()
    {
    auto v = parse_term();
    for (bool done = false; !done;) {
    try { lexer->peek(); }
    catch (C) { lexer->drop();v += parse_term(); }
    catch (C) { lexer->drop();v -= parse_term(); }
    catch (...) { done = true; }
    }
    return v;
    }
    double calculator::parse_term()
    {
    auto v = parse_factor();
    for (bool done = false; !done;) {
    try { lexer->peek(); }
    catch (C) { lexer->drop(); v /= parse_factor(); }
    catch (C) { lexer->drop(); v *= parse_factor(); }
    catch (...) { done = true; }
    }
    return v;
    }
    double calculator::parse_factor()
    {
    try { lexer->next_token(); }
    catch (ident var) { return lookup(var.value);}
    catch (number n) { return n.value;}
    catch (C) { return parse_term(); }
    catch (C) { return -parse_term(); }
    catch (C) { return parse_paren(); }
    catch (...) {}
    throw "unexpected";
    }
    double calculator::parse_paren()
    {
    auto v = parse_expr();
    try { lexer next_token(); }

    catch (C) { return v; }
    catch (...) { }
    throw "expected ')'";
    }
    double calculator::parse_forget_var()
    {
    try { lexer->next_token();}
    catch (const ident& i) { return drop_variable(i.value);}
    catch (...) { throw "expected variable name";}
    }
    double calculator::parse_remember_var()
    {
    std::string id;
    try { lexer->next_token(); }
    catch (ident i) { id = std::move(i.value);}
    catch (...) { throw "expected variable name";}
    try { lexer->next_token(); }
    catch (C) {}
    catch (...) { throw "expected =";}
    auto v = parse_expr();
    memory[id] = v;
    return v;
    }

    View Slide

  13. Exceptional Type Safety, C++ Stockholm 0x07, Björn Fahller @bjorn_fahller
    double calculator::parse(std::string s)
    {
    lexer.reset(new lex(std::move(s)));
    try { lexer->peek(); }
    catch (remember){ lexer->drop(); return parse_remember_var();}
    catch (forget) { lexer->drop(); return parse_forget_var();}
    catch (...) { return parse_expr();}
    try { lexer->next_token(); }
    catch (eof) { }
    catch (...) { throw "garbage after expr";}
    }
    double calculator::parse_expr()
    {
    auto v = parse_term();
    for (bool done = false; !done;) {
    try { lexer->peek(); }
    catch (C) { lexer->drop();v += parse_term(); }
    catch (C) { lexer->drop();v -= parse_term(); }
    catch (...) { done = true; }
    }
    return v;
    }
    double calculator::parse_term()
    {
    auto v = parse_factor();
    for (bool done = false; !done;) {
    try { lexer->peek(); }
    catch (C) { lexer->drop(); v /= parse_factor(); }
    catch (C) { lexer->drop(); v *= parse_factor(); }
    catch (...) { done = true; }
    }
    return v;
    }
    double calculator::parse_factor()
    {
    try { lexer->next_token(); }
    catch (ident var) { return lookup(var.value);}
    catch (number n) { return n.value;}
    catch (C) { return parse_term(); }
    catch (C) { return -parse_term(); }
    catch (C) { return parse_paren(); }
    catch (...) {}
    throw "unexpected";
    }
    double calculator::parse_paren()
    {
    auto v = parse_expr();
    try { lexer next_token(); }

    catch (C) { return v; }
    catch (...) { }
    throw "expected ')'";
    }
    double calculator::parse_forget_var()
    {
    try { lexer->next_token();}
    catch (const ident& i) { return drop_variable(i.value);}
    catch (...) { throw "expected variable name";}
    }
    double calculator::parse_remember_var()
    {
    std::string id;
    try { lexer->next_token(); }
    catch (ident i) { id = std::move(i.value);}
    catch (...) { throw "expected variable name";}
    try { lexer->next_token(); }
    catch (C) {}
    catch (...) { throw "expected =";}
    auto v = parse_expr();
    memory[id] = v;
    return v;
    }
    double calculator::parse_factor()
    {
    try { lexer->next_token(); }
    catch (ident var) { return lookup(var.value);}
    catch (number n) { return n.value;}
    catch (C) { return parse_term(); }
    catch (C) { return -parse_term(); }
    catch (C) { return parse_paren(); }
    catch (...) {}
    throw "unexpected";
    }

    View Slide

  14. Exceptional Type Safety, C++ Stockholm 0x07, Björn Fahller @bjorn_fahller
    double calculator::parse(std::string s)
    {
    lexer.reset(new lex(std::move(s)));
    try { lexer->peek(); }
    catch (remember){ lexer->drop(); return parse_remember_var();}
    catch (forget) { lexer->drop(); return parse_forget_var();}
    catch (...) { return parse_expr();}
    try { lexer->next_token(); }
    catch (eof) { }
    catch (...) { throw "garbage after expr";}
    }
    double calculator::parse_expr()
    {
    auto v = parse_term();
    for (bool done = false; !done;) {
    try { lexer->peek(); }
    catch (C) { lexer->drop();v += parse_term(); }
    catch (C) { lexer->drop();v -= parse_term(); }
    catch (...) { done = true; }
    }
    return v;
    }
    double calculator::parse_term()
    {
    auto v = parse_factor();
    for (bool done = false; !done;) {
    try { lexer->peek(); }
    catch (C) { lexer->drop(); v /= parse_factor(); }
    catch (C) { lexer->drop(); v *= parse_factor(); }
    catch (...) { done = true; }
    }
    return v;
    }
    double calculator::parse_factor()
    {
    try { lexer->next_token(); }
    catch (ident var) { return lookup(var.value);}
    catch (number n) { return n.value;}
    catch (C) { return parse_term(); }
    catch (C) { return -parse_term(); }
    catch (C) { return parse_paren(); }
    catch (...) {}
    throw "unexpected";
    }
    double calculator::parse_paren()
    {
    auto v = parse_expr();
    try { lexer next_token(); }

    catch (C) { return v; }
    catch (...) { }
    throw "expected ')'";
    }
    double calculator::parse_forget_var()
    {
    try { lexer->next_token();}
    catch (const ident& i) { return drop_variable(i.value);}
    catch (...) { throw "expected variable name";}
    }
    double calculator::parse_remember_var()
    {
    std::string id;
    try { lexer->next_token(); }
    catch (ident i) { id = std::move(i.value);}
    catch (...) { throw "expected variable name";}
    try { lexer->next_token(); }
    catch (C) {}
    catch (...) { throw "expected =";}
    auto v = parse_expr();
    memory[id] = v;
    return v;
    }

    View Slide

  15. Exceptional Type Safety, C++ Stockholm 0x07, Björn Fahller @bjorn_fahller
    double calculator::parse(std::string s)
    {
    lexer.reset(new lex(std::move(s)));
    try { lexer->peek(); }
    catch (remember){ lexer->drop(); return parse_remember_var();}
    catch (forget) { lexer->drop(); return parse_forget_var();}
    catch (...) { return parse_expr();}
    try { lexer->next_token(); }
    catch (eof) { }
    catch (...) { throw "garbage after expr";}
    }
    double calculator::parse_expr()
    {
    auto v = parse_term();
    for (bool done = false; !done;) {
    try { lexer->peek(); }
    catch (C) { lexer->drop();v += parse_term(); }
    catch (C) { lexer->drop();v -= parse_term(); }
    catch (...) { done = true; }
    }
    return v;
    }
    double calculator::parse_term()
    {
    auto v = parse_factor();
    for (bool done = false; !done;) {
    try { lexer->peek(); }
    catch (C) { lexer->drop(); v /= parse_factor(); }
    catch (C) { lexer->drop(); v *= parse_factor(); }
    catch (...) { done = true; }
    }
    return v;
    }
    double calculator::parse_factor()
    {
    try { lexer->next_token(); }
    catch (ident var) { return lookup(var.value);}
    catch (number n) { return n.value;}
    catch (C) { return parse_term(); }
    catch (C) { return -parse_term(); }
    catch (C) { return parse_paren(); }
    catch (...) {}
    throw "unexpected";
    }
    double calculator::parse_paren()
    {
    auto v = parse_expr();
    try { lexer->next_token(); }
    catch (C) { return v; }
    catch (...) { }
    throw "expected ')'";
    }
    double calculator::parse_forget_var()
    {
    try { lexer->next_token();}
    catch (const ident& i) { return drop_variable(i.value);}
    catch (...) { throw "expected variable name";}
    }
    double calculator::parse_remember_var()
    {
    std::string id;
    try { lexer->next_token(); }
    catch (ident i) { id = std::move(i.value);}
    catch (...) { throw "expected variable name";}
    try { lexer->next_token(); }
    catch (C) {}
    catch (...) { throw "expected =";}
    auto v = parse_expr();
    memory[id] = v;
    return v;
    }
    double calculator::parse_paren()
    {
    auto v = parse_expr();
    try { lexer->next_token(); }
    catch (C) { return v; }
    catch (...) { }
    throw "expected ')'";
    }

    View Slide

  16. Exceptional Type Safety, C++ Stockholm 0x07, Björn Fahller @bjorn_fahller
    double calculator::parse(std::string s)
    {
    lexer.reset(new lex(std::move(s)));
    try { lexer->peek(); }
    catch (remember){ lexer->drop(); return parse_remember_var();}
    catch (forget) { lexer->drop(); return parse_forget_var();}
    catch (...) { return parse_expr();}
    try { lexer->next_token(); }
    catch (eof) { }
    catch (...) { throw "garbage after expr";}
    }
    double calculator::parse_expr()
    {
    auto v = parse_term();
    for (bool done = false; !done;) {
    try { lexer->peek(); }
    catch (C) { lexer->drop();v += parse_term(); }
    catch (C) { lexer->drop();v -= parse_term(); }
    catch (...) { done = true; }
    }
    return v;
    }
    double calculator::parse_term()
    {
    auto v = parse_factor();
    for (bool done = false; !done;) {
    try { lexer->peek(); }
    catch (C) { lexer->drop(); v /= parse_factor(); }
    catch (C) { lexer->drop(); v *= parse_factor(); }
    catch (...) { done = true; }
    }
    return v;
    }
    double calculator::parse_factor()
    {
    try { lexer->next_token(); }
    catch (ident var) { return lookup(var.value);}
    catch (number n) { return n.value;}
    catch (C) { return parse_term(); }
    catch (C) { return -parse_term(); }
    catch (C) { return parse_paren(); }
    catch (...) {}
    throw "unexpected";
    }
    double calculator::parse_paren()
    {
    auto v = parse_expr();
    try { lexer next_token(); }

    catch (C) { return v; }
    catch (...) { }
    throw "expected ')'";
    }
    double calculator::parse_forget_var()
    {
    try { lexer->next_token();}
    catch (const ident& i) { return drop_variable(i.value);}
    catch (...) { throw "expected variable name";}
    }
    double calculator::parse_remember_var()
    {
    std::string id;
    try { lexer->next_token(); }
    catch (ident i) { id = std::move(i.value);}
    catch (...) { throw "expected variable name";}
    try { lexer->next_token(); }
    catch (C) {}
    catch (...) { throw "expected =";}
    auto v = parse_expr();
    memory[id] = v;
    return v;
    }
    Return error messages

    View Slide

  17. Exceptional Type Safety, C++ Stockholm 0x07, Björn Fahller @bjorn_fahller
    double calculator::parse(std::string s)
    {
    lexer.reset(new lex(std::move(s)));
    try { lexer->peek(); }
    catch (remember){ lexer->drop(); return parse_remember_var();}
    catch (forget) { lexer->drop(); return parse_forget_var();}
    catch (...) { return parse_expr();}
    try { lexer->next_token(); }
    catch (eof) { }
    catch (...) { throw "garbage after expr";}
    }
    double calculator::parse_expr()
    {
    auto v = parse_term();
    for (bool done = false; !done;) {
    try { lexer->peek(); }
    catch (C) { lexer->drop();v += parse_term(); }
    catch (C) { lexer->drop();v -= parse_term(); }
    catch (...) { done = true; }
    }
    return v;
    }
    double calculator::parse_term()
    {
    auto v = parse_factor();
    for (bool done = false; !done;) {
    try { lexer->peek(); }
    catch (C) { lexer->drop(); v /= parse_factor(); }
    catch (C) { lexer->drop(); v *= parse_factor(); }
    catch (...) { done = true; }
    }
    return v;
    }
    double calculator::parse_factor()
    {
    try { lexer->next_token(); }
    catch (ident var) { return lookup(var.value);}
    catch (number n) { return n.value;}
    catch (C) { return parse_term(); }
    catch (C) { return -parse_term(); }
    catch (C) { return parse_paren(); }
    catch (...) {}
    throw "unexpected";
    }
    double calculator::parse_paren()
    {
    auto v = parse_expr();
    try { lexer next_token(); }

    catch (C) { return v; }
    catch (...) { }
    throw "expected ')'";
    }
    double calculator::parse_forget_var()
    {
    try { lexer->next_token();}
    catch (const ident& i) { return drop_variable(i.value);}
    catch (...) { throw "expected variable name";}
    }
    double calculator::parse_remember_var()
    {
    std::string id;
    try { lexer->next_token(); }
    catch (ident i) { id = std::move(i.value);}
    catch (...) { throw "expected variable name";}
    try { lexer->next_token(); }
    catch (C) {}
    catch (...) { throw "expected =";}
    auto v = parse_expr();
    memory[id] = v;
    return v;
    }
    Return error messages
    and throw polymorphic information
    upp the call chain.

    View Slide

  18. Exceptional Type Safety, C++ Stockholm 0x07, Björn Fahller @bjorn_fahller
    double calculator::parse(std::string s)
    {
    lexer.reset(new lex(std::move(s)));
    try { lexer->peek(); }
    catch (remember){ lexer->drop(); return parse_remember_var();}
    catch (forget) { lexer->drop(); return parse_forget_var();}
    catch (...) { return parse_expr();}
    try { lexer->next_token(); }
    catch (eof) { }
    catch (...) { throw "garbage after expr";}
    }
    double calculator::parse_expr()
    {
    auto v = parse_term();
    for (bool done = false; !done;) {
    try { lexer->peek(); }
    catch (C) { lexer->drop();v += parse_term(); }
    catch (C) { lexer->drop();v -= parse_term(); }
    catch (...) { done = true; }
    }
    return v;
    }
    double calculator::parse_term()
    {
    auto v = parse_factor();
    for (bool done = false; !done;) {
    try { lexer->peek(); }
    catch (C) { lexer->drop(); v /= parse_factor(); }
    catch (C) { lexer->drop(); v *= parse_factor(); }
    catch (...) { done = true; }
    }
    return v;
    }
    double calculator::parse_factor()
    {
    try { lexer->next_token(); }
    catch (ident var) { return lookup(var.value);}
    catch (number n) { return n.value;}
    catch (C) { return parse_term(); }
    catch (C) { return -parse_term(); }
    catch (C) { return parse_paren(); }
    catch (...) {}
    throw "unexpected";
    }
    double calculator::parse_paren()
    {
    auto v = parse_expr();
    try { lexer next_token(); }

    catch (C) { return v; }
    catch (...) { }
    throw "expected ')'";
    }
    double calculator::parse_forget_var()
    {
    try { lexer->next_token();}
    catch (const ident& i) { return drop_variable(i.value);}
    catch (...) { throw "expected variable name";}
    }
    double calculator::parse_remember_var()
    {
    std::string id;
    try { lexer->next_token(); }
    catch (ident i) { id = std::move(i.value);}
    catch (...) { throw "expected variable name";}
    try { lexer->next_token(); }
    catch (C) {}
    catch (...) { throw "expected =";}
    auto v = parse_expr();
    memory[id] = v;
    return v;
    }
    Return error messages
    and throw polymorphic information
    upp the call chain.

    View Slide

  19. Exceptional Type Safety, C++ Stockholm 0x07, Björn Fahller @bjorn_fahller
    Björn Fahller
    https://github.com/rollbear/variant_parse
    [email protected]
    @bjorn_fahller
    @rollbear cpplang, swedencpp
    Exceptional Type Safety

    View Slide