4
\$\begingroup\$

I found myself often in the position that I want to have multiple arguments in a constructor which could be in any order.

For example:

struct A;
struct B;
struct C;

struct D {
  D(A a, B b, C c);
  D(A a, C c);
  D(B b, C c);
  // and possibly many more ...
};

This happens to me especially when I heavily use policy based design and privately inherit from more than one class where the bases are allowed to be default constructible or not.

This is why I decided today to make a function named forward_or_default_construct which finds for a given type T the corresponding qualified-T-argument from a given parameter pack and forwards it or, if not found, default constructs T. I also tried to make it SFINAE-friendly.

Since this is a heterogenous type computation I thought to give boost::hana a first-time try. That said, it might be clear, that my hana-fu is quite weak and I have the feeling to use it really clumsy. I appreciate any improvements and comments.

#include "boost/hana.hpp"

namespace detail {
namespace hana = boost::hana;

template <typename T, typename S>
    constexpr auto
    is_copy_or_movable_from(hana::basic_type<T>, hana::basic_type<S>)
    noexcept
    {
        return std::conjunction<
                std::is_same<T, std::decay_t<S>>,
                std::is_constructible<T, S>
        >{};
    }

template <typename T, typename ...Args>
    constexpr bool is_copy_or_movable_from_any_of(
            hana::basic_type<T> t,
            hana::basic_type<Args> ...)
    noexcept
    {
        return hana::any_of(
            hana::tuple_t<Args...>,
            [t](auto s){ return is_copy_or_movable_from(t, s); }
        );
    }

template <typename T, typename ...Args>
    inline constexpr bool is_forward_or_default_constructible_v =
        is_copy_or_movable_from_any_of(hana::type_c<T>, hana::type_c<Args>...)
            || std::is_default_constructible_v<T>;

template <std::size_t N, typename T, typename Arg, typename ...Args>
    constexpr auto find_forwardable_index_impl(
            hana::size_t<N>,
            hana::basic_type<T> t,
            hana::basic_type<Arg>,
            hana::basic_type<Args> ...args)
    noexcept
    {
        if constexpr(is_copy_or_movable_from(t, hana::type_c<Arg>)) {
            return hana::just(hana::size_c<N>);
        } else if constexpr(sizeof...(Args) != 0) {
            return find_forwardable_index_impl(hana::size_c<N+1>, t, args...);
        } else {
            return hana::nothing;
        }
    }

template <typename T, typename ...Args>
    constexpr auto find_forwardable_index(
            hana::basic_type<T> t,
            hana::basic_type<Args> ...args)
    noexcept
    {
        if constexpr(sizeof...(Args) != 0
                && is_copy_or_movable_from_any_of(t, args ...)) {
            return find_forwardable_index_impl(hana::size_c<0>, t, args...);
        } else {
            return hana::nothing;
        }
    }

template <std::size_t N, typename Arg, typename ...Args>
    constexpr decltype(auto)
    forward_n(hana::size_t<N>, Arg&& arg, Args&& ...args)
    noexcept
    {
        if constexpr(N == 0) {
            return std::forward<Arg>(arg);
        } else {
            return forward_n(hana::size_c<N-1>, std::forward<Args>(args)...);
        }
    }

template <typename T, typename ...Args>
    constexpr bool is_nothrow_forward_or_default_constructible_(
            hana::basic_type<T> t, hana::basic_type<Args> ...args)
    noexcept
    {
        if constexpr(find_forwardable_index(t, args...) == hana::nothing) {
            return std::is_nothrow_default_constructible_v<T>;
        } else {
            return true;
        }
    }

template <typename T, typename ...Args>
    inline constexpr bool is_nothrow_forward_or_default_constructible_v =
        is_nothrow_forward_or_default_constructible_(hana::type_c<T>, hana::type_c<Args>...);

/// \brief Forward construct `T` from `args...` if present. Otherwise
///        uses `T`s default constructor.
///
template <typename T, typename ...Args, typename =
    std::enable_if_t<
        is_forward_or_default_constructible_v<T, Args...>
    >>
    constexpr decltype(auto)
    forward_or_default_construct(Args&& ...args)
    noexcept(is_nothrow_forward_or_default_constructible_v<T, Args...>)
    {
        constexpr auto i = find_forwardable_index(
                hana::type_c<T>,
                hana::type_c<Args>...
        );
        if constexpr(i == hana::nothing) {
            return T();
        } else {
            return forward_n(*i, std::forward<Args>(args)...);
        }
    }

} // !namespace detail

I tested it today very barely with this:

// #include the upper file

#include <cassert>
#include <iostream>

struct A {
    int foo;
    A() { std::cout << "default construted A\n"; }
    explicit A(int f): foo{f} {}
    A(const A& a): foo{a.foo} { std::cout << "copy constructed A\n"; } 
    A(A&& a): foo{a.foo} { std::cout << "move constructed A\n"; } 
    A& operator=(const A& a) { foo = a.foo; std::cout << "copy assigned A\n"; return *this; }
    A& operator=(A&& a) { foo = a.foo; std::cout << "moved assigned A\n"; return *this; }
};

struct B {
    int bar;
    B() { std::cout << "default constructed B\n"; }
    explicit B(int f): bar{f} {}
    B(const B& a): bar{a.bar} { std::cout << "copy constructed B\n"; } 
    B(B&& a): bar{a.bar} { std::cout << "move constructed B\n"; } 
    B& operator=(const B& a) { bar = a.bar; std::cout << "copy assigned B\n"; return *this; }
    B& operator=(B&& a) { bar = a.bar; std::cout << "moved assigned B\n"; return *this; }
};

struct C {
    int xxx;
};

struct D {
    int yyy;
};

struct E {
    A a;
    B b;
    C c;

    template <typename ...Args, typename =
        std::enable_if_t<
            boost::hana::is_subset(
                    boost::hana::transform(
                        boost::hana::tuple_t<Args ...>,
                        boost::hana::metafunction<std::decay>),
                    boost::hana::tuple_t<A,B,C>) &&
            detail::is_forward_or_default_constructible_v<A, Args...> &&
            detail::is_forward_or_default_constructible_v<B, Args...> &&
            detail::is_forward_or_default_constructible_v<C, Args...>   >>
    E(Args&& ...args)
    : a{detail::forward_or_default_construct<A>(std::forward<Args>(args)...)}
    , b{detail::forward_or_default_construct<B>(std::forward<Args>(args)...)}
    , c{detail::forward_or_default_construct<C>(std::forward<Args>(args)...)}
    {}
};

int main()
{
    namespace hana = boost::hana;
    using namespace detail;

    {
        A a{42};
        B b{24};

        // Output of next 5 lines is
        //
        // move constructed A
        // copy constructed B
        // default construted A
        // default constructed B
        // <nothing here> (nothing gets created)

        A a_{detail::forward_or_default_construct<A>(b, std::move(a))};
        B b_{detail::forward_or_default_construct<B>(b, std::move(a))};
        A a2{detail::forward_or_default_construct<A>()};
        auto&& b2{detail::forward_or_default_construct<B>(a)};
        auto&& b3{detail::forward_or_default_construct<B>(a, std::move(b))};

        assert(a_.foo == 42);
        assert(b_.bar == 24);
        assert(a2.foo == 0);
        assert(b2.bar == 0);
        assert(b3.bar == 24);
    }

    {
        constexpr C c{111};
        constexpr D d{222};
        constexpr auto c_{detail::forward_or_default_construct<C>(d, c)};
        static_assert(c_.xxx == 111);
    }

    {
        A a{42};
        C c{42};

        // Ouput of next line is
        //
        // move constructed A
        // default constructed B 

        auto e = E(std::move(a), c);

        assert(e.c.xxx == 42);
    }
}

Here is the proof that it works in Wandbox.

\$\endgroup\$
  • \$\begingroup\$ If someone could add a boost-hana-tag I would be glad. I can not. \$\endgroup\$ – Maikel Mar 13 '17 at 21:47
  • \$\begingroup\$ there is a boost tag, which I believe should be enough. \$\endgroup\$ – Incomputable Mar 14 '17 at 8:15
  • \$\begingroup\$ I added a link to an online compiler and changed the code a little, since I looked this morning again on it... I hope this is ok and no-one started something big yet.. Cheers and thanks for any comments. \$\endgroup\$ – Maikel Mar 14 '17 at 9:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.