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.
boost-hana
-tag I would be glad. I can not. \$\endgroup\$boost
tag, which I believe should be enough. \$\endgroup\$