C++ Logo

std-proposals

Advanced search

[std-proposals] template catch block

From: Frederick Virchanza Gotham <cauldwell.thomas_at_[hidden]>
Date: Sat, 23 Sep 2023 12:08:37 +0100
Eight months ago I suggested on the mailing list here that Runtime
Type Identification (RTTI) should be available inside a 'catch(...)'
block, as I described in this paper:

    http://www.virjacode.com/download/RTTI_current_exception_latest.pdf

Following on from that, I would like to propose the new idea of a
'template catch block'. Currently with C++23, we write try-catch as
follows:

    try
    {

    }
    catch(std::exception const &e)
    {

    }
    catch(...)
    {

    }

I propose that we can stick another catch-block between those two, as follows:

    try
    {

    }
    catch(std::exception const &e)
    {

    }
    template<typename T>
    catch(T &obj)
    {
        T tmp{};
        ++tmp;
        obj += tmp;
        SomeFunc(obj);
    }
    catch(...)
    {

    }

Inside the second-last catch-block, the compiler has the RTTI of the
thrown object at runtime, but at compile-time it has nothing. And so
the only way the compiler could possibly implement the second-last
catch-block as machine code would be to implement it in machine code
for every intrinsic type, as well as for every user-defined type that
has been seen so far in the translation unit, almost as though it had
been written:

    try
    {

    }
    catch(std::exception const &e)
    {

    }
    catch(char &e)
    {
        char tmp{};
        ++tmp;
        obj += tmp;
        SomeFunc(obj);
    }
    catch(char signed &e)
    {
        char signed tmp{};
        ++tmp;
        obj += tmp;
        SomeFunc(obj);
    }
    catch(char unsigned &e)
    {
        char unsigned tmp{};
        ++tmp;
        obj += tmp;
        SomeFunc(obj);
    }
    catch(short &e)
    {
        short tmp{};
        ++tmp;
        obj += tmp;
        SomeFunc(obj);
    }
    catch(short unsigned &e)
    {
        short unsigned tmp{};
        ++tmp;
        obj += tmp;
        SomeFunc(obj);
    }
    catch(int &e)
    {
        int tmp{};
        ++tmp;
        obj += tmp;
        SomeFunc(obj);
    }
    catch(unsigned &e)
    {
        unsigned tmp{};
        ++tmp;
        obj += tmp;
        SomeFunc(obj);
    }
    catch(long &e)
    {
        long tmp{};
        ++tmp;
        obj += tmp;
        SomeFunc(obj);
    }
    catch(long unsigned &e)
    {
        long unsigned tmp{};
        ++tmp;
        obj += tmp;
        SomeFunc(obj);
    }
    // ......... and on and on and on for every type
    catch(...)
    {

    }

If the catch-block were to fail to compile for any given type, then
that implementation of the catch-block would be discarded (and so
instead it will enter the 'catch(...)' block below it).

Of course one of the drawbacks of this new proposed feature would be
the bloat in size of machine code, but nowadays we have gigs of RAM
and terras of disk space so it's not a big deal. This bloat in machine
code size could be lessened by discarding the implementations that are
identical -- for example on most computers the machine code
implementations for 'signed long' and 'unsigned long' will be exactly
the same (depending of course on a few things such as whether
'SomeFunc' is expanded inline and if the inline expansion is exactly
the same for both types).

Perhaps we could also put constraints on the catch-block:

    template<typename T>
    requires std::is_polymorphic_v<T>
    catch (T &obj)
    {
        T tmp{};
        ++tmp;
        obj += tmp;
        SomeFunc(obj);
    }

If you were to ask me how this new feature would be useful, well
here's one simple example:

    template<typename T>
    catch(T &obj)
    {
        cout << obj << endl;
    }

Compiler vendors who implement this new feature could also use the
same implementation to write an "std::visit" that can work with an
"std::any" object, as follows:

    #include <any> // any
    #include <iostream> // cout, endl
    #include <vector> // vector

    using std::cout, std::endl;

    int main(void)
    {
        std::vector<std::any> vec = {1, 3.14, "hello"};

        for ( auto &e : vec )
        {
            std::visit([](auto &&arg){ cout << arg << endl; }, e);
        }

        return 0;
    }

This new feature I'm proposing has many advantages, and only two disadvantages:
(1) Bloat in size of machine code
(2) Increase in compilation time

Both of these disadvantages are mitigated on modern PC's with gigs of
RAM and terras of space.

Received on 2023-09-23 11:08:50