atomgalaxy/libciabatta

is_mixin type trait

Opened this issue · 1 comments

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>);

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.