Tag Archives: template metaprogramming

A different way to work with exceptions: expected

Background

First I recommend watching the wonderful talk  given by Andrei Alexandrescu about error handling in modern c++ and also viewing the accompanying slides.

Thanks to this talk, I was inspired to try my hand at implementing one possible version of the expected<T> that he presents in his talk. However, I was more inclined to agree with some of the criticisms that Herb Sutter made; namely, I wanted to try an implementation using SFINAE and the restriction that the stored exception type must be derived from std::exception. Moreover, I wanted to add one or two other convenience functions modeled loosely on what the stl provides. Also, as I do most of my casual development in MSVC, I wanted my implementation to compile with the 2012 CTP version of the compiler.

Final usage

// from an int
expected<int> expected_int1(10);

// true
bool valid = expected_int1.is_valid();

// 10
auto a = expected_int1.get();

// nullptr
auto ptr = expected_int1.get_exception<std::runtime_error>();

// false
bool has_exception = expected_int1.with_exception([](std::runtime_error& e) {
    // won't be called
});

// from an exception
expected<int> expected_int2(std::runtime_error("oops"));

// false
valid = expected_int2.is_valid();

// std::exception_ptr
ptr = expected_int2.get_exception<std::runtime_error>();

// true
has_exception = expected_int2.with_exception([](std::runtime_error& e) {
    std::clog << e.what() << std::endl; // "oops"
});

try {
    a = expected_int2.get(); // throws
} catch (std::runtime_error& e) {
    std::clog << e.what() << std::endl; // "oops"
}

// from code
expected<std::ifstream> expected_file([] {
    std::ifstream in("no file");
    in.exceptions(in.failbit);

    return in;
});

// prints the message and then rethrows
expected_file.with_exception<true>([](std::ios_base::failure& e) {
    std::clog << e.code().message() << std::endl;
});

struct foo {
    foo(int a, bool b, double c, char const* d) {}
};

auto expected_foo = make_expected<foo>(10, false, 11.0, "bar");

Complications

The implementation that is presented in the talk unabashedly makes use of any appropriate features offered by C++11 — critically, unrestricted unions. Unfortunately these are not yet supported by visual studio, hence the need to work around it. The work around isn’t hard per say, but it makes the implementation a little messier. We need two things:

Properly aligned storage

Nothing too hard, but note the use of two new standard library types: std::aligned_storage and std::alignment_of.

//! compile time max(A, B)
template <unsigned A, unsigned B>
struct max_of {
    static unsigned const value = B < A ? A : B;
};

//! workaround for the lack of C++11 unions in MSVC
typedef typename std::aligned_storage<
    max_of<sizeof(T), sizeof(std::exception_ptr)>::value,
    max_of<std::alignment_of<T>::value, std::alignment_of<std::exception_ptr>::value>::value
>::type storage_type_;

//! workaround for the lack of C++11 unions in MSVC
storage_type_ storage_;

Access to the two mutually exclusive values

Again, nothing particularly burdensome; just a little casting.

//! workaround for the lack of C++11 unions in MSVC
T& as_value_() {
    return *reinterpret_cast<T*>(&storage_);
}
//! workaround for the lack of C++11 unions in MSVC
T const& as_value_() const {
    return *reinterpret_cast<T*>(&storage_);
}

//! workaround for the lack of C++11 unions in MSVC
std::exception_ptr& as_exception_() {
    return *reinterpret_cast<std::exception_ptr*>(&storage_);
}
//! workaround for the lack of C++11 unions in MSVC
std::exception_ptr const& as_exception_() const {
    return *reinterpret_cast<std::exception_ptr*>(&storage_);
}

Differences and Refinements

The original talk provides static member functions for construction from an exception or from “code”. I decided to restrict my implementation to exceptions derived from std::exception, and as such could use regular member constructors instead.

Construction from an exception

Again, as in the talk above, the compile time and run-time typeids must match, or else the exception object will have been sliced: Throw by value, catch by reference.

////////////////////////////////////////////////////////////////////////////
//! Construct from an exception derived from std::exception
////////////////////////////////////////////////////////////////////////////
template <typename E>
expected(E const& exception,
    //the exception type must be derived from std::exception
    typename std::enable_if<
        std::is_base_of<std::exception, E>::value
    >::type* = nullptr
) : is_expected_(false) {
    //! the dynamic and static types must match or the object will have
    //! sliced.
    if (typeid(E) != typeid(exception)) {
        assert(false && "sliced!");
    }

    new(&storage_) std::exception_ptr(std::make_exception_ptr(exception));
}

Construction from a callable type

Here the constructor is constrained to nullary callable types by the second unnamed type: if F doesn’t define a nullary operator() SFINAE will exclude this overload.

////////////////////////////////////////////////////////////////////////////
//! Construct from a nullary callable object
////////////////////////////////////////////////////////////////////////////
template <typename F, typename = decltype(std::declval<F>()())>
expected(F function) : is_expected_(true) {
    try {
        new(&storage_) T(function());
    } catch(...) {
        is_expected_ = false;
        new(&storage_) std::exception_ptr(std::current_exception());
    }
}

Handling, modifying, and rethrowing stored exceptions

A final convenience function I call with_exception makes use of the callable_traits template I introduced in my last post to avoid having to explicitly specify the exception type twice when using it.

//! Decide at compile time whether to rethrow the active exception
template <bool B> static inline void rethrow_exception();
template <> __declspec(noreturn) static inline void rethrow_exception<true>()  { throw; }
template <> static inline void rethrow_exception<false>() { }

////////////////////////////////////////////////////////////////////////////
//! If the stored exception_ptr is convertible to E, call function(E&), and
//! for Rethrow = true, rethrow. Otherwise return true. If the exception_ptr
//! is not convertible to to E, return false.
////////////////////////////////////////////////////////////////////////////
template <bool Rethrow = false, typename F>
bool with_exception(F function) {
    typedef traits::callable_traits<F>::arg<0>::type
        exception_type;
    typedef typename std::remove_reference<exception_type>::type
        non_ref_exception_type;

    static_assert(std::is_reference<exception_type>::value,
        "function must take a reference");
    static_assert(
        std::is_base_of<
            std::exception,
            non_ref_exception_type
        >::value,
        "function must take a type derived from std::exception"
    );

    if (!is_valid()) {
        try {
            std::rethrow_exception(as_exception_());
        } catch (exception_type e) {
            function(e);

            rethrow_exception<Rethrow>();

            return true;
        } catch (...) {
        }
    }

    return false;
}

Making expected objects

One final difference, or addition, is an stl like make_expected helper function, which also happened to reveal an interesting compiler bug in MSVC related to catch(…) and variadic templates. It merely constructs the object with a T or any exception thrown during its construction. Another rather severe compiler bug prevents this from being as useful as it could be, but it will work for simple construction.

namespace detail {
    //! Workaround for a compiler bug in MSVC
    template <typename T, typename F>
    expected<T> make_expected_helper(F function) {
        try {
            return expected<T>(function());
        } catch (...) {
            return expected<T>(std::current_exception());
        }
    }
} //namespace detail

template <typename T, typename... Args>
expected<T> make_expected(Args&&... args) {
    //! workaround for a compiler bug in MSVC
    return detail::make_expected_helper<T>([&] {
        return T(std::forward<Args>(args)...);
    });
}

Final Implementation

//! compile time max(A, B)
template <unsigned A, unsigned B>
struct max_of {
    static unsigned const value = B < A ? A : B;
};

//! Decide at compile time whether to rethrow the active exception
template <bool B> static inline void rethrow_exception();
template <> __declspec(noreturn) static inline void rethrow_exception<true>()  { throw; }
template <> static inline void rethrow_exception<false>() { }

////////////////////////////////////////////////////////////////////////////////
//! Contains a T or an exception derived from std::exception describing the
//! reason a T failed to be created.
////////////////////////////////////////////////////////////////////////////////
template <typename T>
class expected {
public:
    ////////////////////////////////////////////////////////////////////////////
    //! Explicitly destroy T or the exception_ptr
    ////////////////////////////////////////////////////////////////////////////
    ~expected() {
        if (is_valid()) {
            as_value_().~T();
        } else {
            using std::exception_ptr; //only as a workaround for a parsing quirk
            as_exception_().~exception_ptr();
        }
    }

    ////////////////////////////////////////////////////////////////////////////
    //! Construct from another expected
    ////////////////////////////////////////////////////////////////////////////
    expected(expected const& other) : is_expected_(other.is_expected_) {
        if (other.is_valid()) {
            new(&storage_) T(other.as_value_());
        } else {
            new(&storage_) std::exception_ptr(other.as_exception_);
        }
    }
    expected(expected&& other) : is_expected_(other.is_expected_) {
        if (other.is_valid()) {
            new(&storage_) T(std::move(other.as_value_()));
        } else {
            new(&storage_) std::exception_ptr(std::move(other.as_exception_()));
        }
    }

    expected& operator=(expected const& rhs) {
        ~expected(); // clean up the current value;

        is_expected_ = rhs.is_expected_;

        if (is_valid()) {
            new(&storage_) T(rhs.as_value_());
        } else {
            new(&storage_) std::exception_ptr(rhs.as_exception_);
        }

        return *this;
    }

    expected& operator=(expected&& rhs) {
        swap(rhs);
        return *this;
    }

    ////////////////////////////////////////////////////////////////////////////
    //! Construct from a T
    ////////////////////////////////////////////////////////////////////////////
    expected(T const& value) : is_expected_(true) {
        new(&storage_) T(value);
    }
    expected(T&& value) : is_expected_(true) {
        new(&storage_) T(std::move(value));
    }

    ////////////////////////////////////////////////////////////////////////////
    //! Construct from an exception derived from std::exception
    ////////////////////////////////////////////////////////////////////////////
    template <typename E>
    expected(E const& exception,
        //the exception type must be derived from std::exception
        typename std::enable_if<
            std::is_base_of<std::exception, E>::value
        >::type* = nullptr
    ) : is_expected_(false) {
        if (typeid(E) != typeid(exception)) {
            assert(false && "sliced!");
        }

        new(&storage_) std::exception_ptr(std::make_exception_ptr(exception));
    }

    ////////////////////////////////////////////////////////////////////////////
    //! Construct from an exception_ptr
    ////////////////////////////////////////////////////////////////////////////
    expected(std::exception_ptr exception) : is_expected_(false) {
        new(&storage_) std::exception_ptr(std::move(exception));
    }

    ////////////////////////////////////////////////////////////////////////////
    //! Construct from a nullary callable object
    ////////////////////////////////////////////////////////////////////////////
    template <typename F, typename = decltype(std::declval<F>()())>
    expected(F function) : is_expected_(true) {
        try {
            new(&storage_) T(function());
        } catch(...) {
            is_expected_ = false;
            new(&storage_) std::exception_ptr(std::current_exception());
        }
    }

    bool is_valid() const {
        return is_expected_;
    }

    T& get() {
        if (!is_valid()) std::rethrow_exception(as_exception_());
        return as_value_();
    }
    T const& get() const {
        if (!is_valid()) std::rethrow_exception(as_exception_());
        return as_value_();
    }

    //! The std::exception_ptr if the stored exception is an E, otherwise std::exception_ptr(nullptr)
    template <typename E>
    std::exception_ptr get_exception() {
        static_assert(std::is_base_of<std::exception, E>::value,
            "E must be derived from std::exception.");

        if (!is_valid()) {
            try {
                std::rethrow_exception(as_exception_());
            } catch (E const&) {
                return as_exception_();
            } catch (...) {
            }
        }

        return std::exception_ptr(nullptr);
    }

    ////////////////////////////////////////////////////////////////////////////
    //! If the stored exception_ptr is convertible to E, call function(E&), and
    //! for Rethrow = true, rethrow. Otherwise return true. If the exception_ptr
    //! is not convertible to to E, return false.
    ////////////////////////////////////////////////////////////////////////////
    template <bool Rethrow = false, typename F>
    bool with_exception(F function) {
        typedef traits::callable_traits<F>::arg<0>::type
            exception_type;
        typedef typename std::remove_reference<exception_type>::type
            non_ref_exception_type;

        static_assert(std::is_reference<exception_type>::value,
            "function must take a reference");
        static_assert(
            std::is_base_of<
                std::exception,
                non_ref_exception_type
            >::value,
            "function must take a type derived from std::exception"
        );

        if (!is_valid()) {
            try {
                std::rethrow_exception(as_exception_());
            } catch (exception_type e) {
                function(e);

                rethrow_exception<Rethrow>();

                return true;
            } catch (...) {
            }
        }

        return false;
    }

    void swap(expected& rhs) {
        if (is_valid()) {
            //(1) good good
            if (rhs.is_valid()) {
                using std::swap;
                swap(as_value_(),  rhs.as_value_());
            //(2) good bad
            } else {
                auto temp = std::move(rhs.as_exception_());

                new(&rhs.storage_) T(std::move(as_value_()));
                new(&storage_)     std::exception_ptr(std::move(temp));

                std::swap(is_expected_, rhs.is_expected_);
            }
        } else {
            //(3) good bad
            if (rhs.is_valid()) {
                rhs.swap(*this); //complementary case to (2)
            //(4) bad bad
            } else {
                std::swap(as_exception_(),  rhs.as_exception_());
            }
        }
    }
private:
    //! workaround for the lack of C++11 unions in MSVC
    typedef typename std::aligned_storage<
        max_of<sizeof(T), sizeof(std::exception_ptr)>::value,
        max_of<std::alignment_of<T>::value, std::alignment_of<std::exception_ptr>::value>::value
    >::type storage_type_;

    //! workaround for the lack of C++11 unions in MSVC
    storage_type_ storage_;

    bool  is_expected_;

    T& as_value_() {
        return *reinterpret_cast<T*>(&storage_);
    }
    T const& as_value_() const {
        return *reinterpret_cast<T*>(&storage_);
    }

    //! workaround for the lack of C++11 unions in MSVC
    std::exception_ptr& as_exception_() {
        return *reinterpret_cast<std::exception_ptr*>(&storage_);
    }
    //! workaround for the lack of C++11 unions in MSVC
    std::exception_ptr const& as_exception_() const {
        return *reinterpret_cast<std::exception_ptr*>(&storage_);
    }
};

namespace detail {
    //! Workaround for a compiler bug in MSVC
    template <typename T, typename F>
    expected<T> make_expected_helper(F function) {
        try {
            return expected<T>(function());
        } catch (...) {
            return expected<T>(std::current_exception());
        }
    }
} //namespace detail

template <typename T, typename... Args>
expected<T> make_expected(Args&&... args) {
    //! workaround for a compiler bug in MSVC
    return detail::make_expected_helper<T>([&] {
        return T(std::forward<Args>(args)...);
    });
}

template <typename T>
void swap(expected<T>& lhs, expected<T>& rhs) {
    lhs.swap(rhs);
}

1 Comment

Filed under C++, Programming

Defining a traits type for callable objects

While playing around with lambda functions, and functors in general, I quickly discovered that in certain situations it would be very useful to obtain information about a functor’s return type, parameter types, and object type. However, perhaps do to some compiler quirks, the implementation was a bit trickier than I thought it ought to be.

For the final interface we would like to have something like the following.

auto foo = [](int a, bool b, double c) -> void {};
typedef callable_traits<decltype(foo)> traits;

//traits::object_t     is a compiler generated lambda type
//traits::result_t     is void
//traits::args_t       is std::tuple<int, bool, double>
//traits::arg<0>::type is int

So, as a first step, the interface.

template <typename T>
struct callable_traits {
    typedef unspecified object_t;
    typedef unspecified result_t;
    typedef unspecified args_t;

    template <unsigned N>
    struct arg : public std::tuple_element<N, args_t> {};
};

There are now two cases to deal with. (1) A functor (any object which defines operator()) and (2) A free function (pointer). So, we need to define a helper class to select between the cases.

//T is our potential function object; is_ptr is true_type when T is a pointer type
template <typename T, typename is_ptr = typename std::is_pointer<T>::type>
struct callable_helper;

//! specialization for functors (and lambdas)
template <typename T>
struct callable_helper<T, std::false_type> {
    typedef unspecified type;
};

//! specialization for function pointers
template <typename T>
struct callable_helper<T, std::true_type> {
    typedef unspecified  type;
};

Alright, so now we can determine whether we are dealing with a (potential) functor, or just a regular free function pointer. We next need to extract the required information for both cases: another helper class. This time we have three cases. (1) A free function pointer. (2) A non-const member pointer. (3) A const member pointer.

template <typename T>
struct callable_helper_ptr;

//! non-member functions
template <typename R, typename... Args>
struct callable_helper_ptr<R (*)(Args...)> {
    typedef void                object_t;
    typedef R                   result_t;
    typedef std::tuple<Args...> args_t;
};

//! member functions
template <typename R, typename O, typename... Args>
struct callable_helper_ptr<R (O::*)(Args...)> {
    typedef O                   object_t;
    typedef R                   result_t;
    typedef std::tuple<Args...> args_t;
};

//! const member functions
template <typename R, typename O, typename... Args>
struct callable_helper_ptr<R (O::*)(Args...) const> {
    typedef O                   object_t;
    typedef R                   result_t;
    typedef std::tuple<Args...> args_t;
};

Using template specialization we can select between the three cases, and with variadic templates we can capture the function parameters in a std::tuple. Gluing it all together and filling in the unspecified portions with our helper classes we arrive at the final implementation.

namespace detail {
    ////////////////////////////////////////////////////////////////////////////
    //! Select between function pointer types
    ////////////////////////////////////////////////////////////////////////////
    template <typename T>
    struct callable_helper_ptr;

    //! non-member functions
    template <typename R, typename... Args>
    struct callable_helper_ptr<R (*)(Args...)> {
        typedef void                object_t;
        typedef R                   result_t;
        typedef std::tuple<Args...> args_t;
    };

    //! member functions
    template <typename R, typename O, typename... Args>
    struct callable_helper_ptr<R (O::*)(Args...)> {
        typedef O                   object_t;
        typedef R                   result_t;
        typedef std::tuple<Args...> args_t;
    };

    //! const member functions
    template <typename R, typename O, typename... Args>
    struct callable_helper_ptr<R (O::*)(Args...) const> {
        typedef O                   object_t;
        typedef R                   result_t;
        typedef std::tuple<Args...> args_t;
    };

    ////////////////////////////////////////////////////////////////////////////
    //! Select between function pointers and functors
    ////////////////////////////////////////////////////////////////////////////
    template <typename T, typename is_ptr = typename std::is_pointer<T>::type>
    struct callable_helper;

    //! specialization for functors (and lambdas)
    template <typename T>
    struct callable_helper<T, std::false_type> {
        typedef callable_helper_ptr<decltype(&T::operator())> type;
    };

    //! specialization for function pointers
    template <typename T>
    struct callable_helper<T, std::true_type> {
        typedef callable_helper_ptr<T> type;
    };
} //namespace detail

////////////////////////////////////////////////////////////////////////////////
//! defines the various details of a callable object T
////////////////////////////////////////////////////////////////////////////////
template <typename T>
struct callable_traits {
    typedef typename detail::callable_helper<T>::type::object_t object_t;
    typedef typename detail::callable_helper<T>::type::result_t result_t;
    typedef typename detail::callable_helper<T>::type::args_t   args_t;

    template <unsigned N>
    struct arg : public std::tuple_element<N, args_t> {};
};

I’ll address what this might be useful for in a following post about smart pointers.

1 Comment

Filed under C++, Programming