is_mixin type trait
Opened this issue · 1 comments
psalvaggio commented
I was working with the approach from this library and I found the following type trait to be useful. It might be worth including in your library if you agree:
template <typename T>
struct is_mixin {
private:
template <template <class> class... Mixins>
static decltype(
static_cast<const mixin<T, Mixins...>>(std::declval<T>()),
std::true_type{})
test(const mixin<T, Mixins...>&);
static std::false_type test(...);
public:
static constexpr bool value =
decltype(is_mixin::test(std::declval<T>()))::value;
};
template <typename T>
inline constexpr bool is_mixin_v = is_mixin<T>::value;
Usage:
template <typename Base>
struct A {
CIABATTA_DEFAULT_MIXIN_CTOR(A, Base);
int foo;
};
struct B : ciabatta::mixin<B, A> {};
static_assert(is_mixin_v<B>);
static_assert(!is_mixin_v<A>);
psalvaggio commented
One other thing that I found useful was mixin_cast<>
, which allowed upcasting a mixin class to a base class for a single mixin type:
namespace detail {
/** Base template to return the type of the base class of the mixin class for
* the given mixin type
*
* \tparam Concrete The concrete mixin class
* \tparam Mixins Mixin to search fro and the list of mixins in \c Concrete
*/
template <typename Concrete, template <class> class... Mixins>
struct mixin_base {
template <typename T>
struct always_false {
static constexpr bool value = false;
};
static_assert(
always_false<Concrete>::value,
"mixin_base called with a mixin that isn't in Concrete");
};
/** General (non-matched) search case for mixin_base
*
* \tparam Concrete The concrete mixin class
* \tparam Mixin The mixin we are searching for
* \tparam First The first mixin in the current mixin chain
* \tparam Mixins The rest of the mixins in \c Concrete
*/
template <
typename Concrete,
template <class>
class Mixin,
template <class>
class First,
template <class>
class... Mixins>
struct mixin_base<Concrete, Mixin, First, Mixins...>
: mixin_base<Concrete, Mixin, Mixins...> {};
/** Base (matched) case for mixin_base
*
* \tparam Concrete The concrete mixin class
* \tparam Mixin The mixin we are searching for
* \tparam Mixins The rest of the mixins in \c Concrete
*/
template <
typename Concrete,
template <class>
class Mixin,
template <class>
class... Mixins>
struct mixin_base<Concrete, Mixin, Mixin, Mixins...> {
using type = mixin_impl<Concrete, Mixin, Mixins...>;
};
}
/** Metafunction for the base class of a concrete mixin class with the given
* mixin type.
*
* \tparam Concrete The concrete mixin class
* \tparam Mixin The mixin for which we desire the base class
*/
template <typename Concrete, template <class> class Mixin>
struct mixin_base {
private:
template <template <class> class... Mixins>
static decltype(
static_cast<const mixin<Concrete, Mixins...>&>(std::declval<Concrete>()),
typename detail::mixin_base<Concrete, Mixin, Mixins...>::type{})
test(const mixin<Concrete, Mixins...>&);
public:
using type = decltype(mixin_base::test(std::declval<Concrete>()));
static_assert(is_mixin_v<Concrete>, "Concrete must be a mixin");
};
/// Convenience alias for mixin_base
template <typename Concrete, template <class> class Mixin>
using mixin_base_t = typename mixin_base<Concrete, Mixin>::type;
/** Casts a concrete mixin class to a specific mixin base class
*
* \tparam Concrete The concrete mixin class
* \tparam Mixin The mixin to which to cast
*
* \param m The mixin object to cast
*
* \return The casted version of \c m
*/
template <template <class> class Mixin, typename Concrete>
constexpr decltype(auto) mixin_cast(const Concrete& m) {
return static_cast<const mixin_base_t<Concrete, Mixin>&>(m);
}
/// \overload
template <template <class> class Mixin, typename Concrete>
constexpr decltype(auto) mixin_cast(Concrete& m) {
return static_cast<mixin_base_t<Concrete, Mixin>&>(m);
}
It's not a perfect abstraction due to the inheritance chaining. As you can see in the usage below:
template <typename Base>
struct A : Base {
CIABATTA_DEFAULT_MIXIN_CTOR(A, Base);
int a;
};
template <typename Base>
struct B : Base {
CIABATTA_DEFAULT_MIXIN_CTOR(B, Base);
double b;
};
template <typename Base>
struct C : Base {
CIABATTA_DEFAULT_MIXIN_CTOR(C, Base);
char c;
};
int main() {
ABC x;
mixin_cast<A>(x).a = 5;
mixin_cast<B>(x).b = 4.5;
mixin_cast<C>(x).c = 'a';
}
mixin_cast<A>
can also access b
and c
, but mixin_cast<C>
can't access a
or b
. However, I've found this useful for building up hierarchial I/O code where you can teach each mixin how to write itself and then dispatch code based on mixins that compose a concrete class.