The Ultimate Mixin

The Partridge Family were neither partridges nor a family. Discuss.
Post Reply
User avatar
cyboryxmen
Posts: 190
Joined: November 14th, 2014, 2:03 am

The Ultimate Mixin

Post by cyboryxmen » January 24th, 2018, 8:41 am

Mixins are great. They allow you to mix in different components together to make a single class. This gives the class an amazing amount of customisability as you can plug in different components to change its behaviour. It also allows the components to be more independent from each other allowing them to be reused for other classes.

The problem I had with them mostly have to do with encapsulation. To allow the different components to access each other's members, they must make them public exposing their interface.

With my implementation of mixins, the components can choose to make their members protected to allow them to be used only by other components in the mixin. Additionally, A component will still have the freedom to make their members private to to restrict them from everyone but the component itself.

Additionally, I gave the mixin a master component as the component that the mixin will inherit constructors for automating mixin construction. Automating construction was hard because a mixin can have so many components. To prevent the need to initialise them all, only one master component will need to be initialised. If the other components need values that must be initialised, it'll be in the master component. All variables by design should be only in the master component.

Here's a demonstration on how the class might be used:

Code: Select all

#include <iostream>
#include "Mixin.h"

template<typename Accessor>
class Template1
{
protected:
	void Func1 ( )
	{
		std::cout << "Func1 called!\n";
		static_cast< Accessor* >( this )->Func2 ( );
	}
};
template<typename Accessor>
class Template2
{
protected:
	void Func2 ( )
	{
		std::cout << "Func2 called!\n";
		static_cast< Accessor* >( this )->Func3 ( );
	}
};
template<typename Accessor>
class Template3
{
protected:
	void Func3 ( )
	{
		std::cout << "Func3 called!\n";
		static_cast< Accessor* >( this )->FinalFunc ( );
	}
};
template<typename Accessor>
class MasterTemplate
{
public:
	MasterTemplate ( const int num ) : num_ { num }
	{

	}

	void MasterFunc ( )
	{
		std::cout << "Func chain start!\n";
		static_cast< Accessor* >( this )->Func1 ( );
	}

protected:
	void FinalFunc ( )
	{
		std::cout << "Success! Here's your number: " << num_ << "\n";
	}

private:
	int num_;
};

int main ( )
{
	using namespace template_bullshit;

	std::cout << "Enter a number: ";
	int input;
	std::cin >> input;

	Mixin <MasterTemplate, Template1, Template2, Template3> mixin { input };
	mixin.MasterFunc ( );

	system ( "pause" );
	return 0;
}
As you can see, the interface is pretty simple to use. Here's the repository. I hope that you found it as useful as I did.
Zekilk

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: The Ultimate Mixin

Post by albinopapa » June 5th, 2020, 3:51 am

Seeing as how I'm just now getting curious about mixins, I'd say I'm about 2-3 years behind you in terms of ability maybe more lol.

When I look up mixins in C++, it's always about a final class implementing the base classes' interface by simply forwarding the call to a base class. You make a somewhat valid point about encapsulation, but I'm wondering if there is an easier way. For instance, would this be considered a mixin?

Code: Select all

#pragma once

#include "Colors.h"
#include "Vec2.h"
#include "Graphics.h"
#include <vector>

class Sprite {
public:
	Color const* pixels()const noexcept { return m_pixels.data(); }
	int width()const noexcept { return m_width; }
	int height()const noexcept { return m_height; }

private:
	std::vector<Color> m_pixels;
	int m_width = 0;
	int m_height = 0;
};

class HasPosition {
public:
	Vec2 const& position()const noexcept { return m_position };
	void position( Vec2 const& pos )noexcept { m_position = pos };
private:
	Vec2 m_position;
};

class HasVelocity {
public:
	Vec2 const& velocity()const noexcept { return m_velocity; }
	void velocity( Vec2 const& vel )noexcept { m_velocity = vel; }
private:
	Vec2 m_velocity;
};

class HasSprite {
public:
	Sprite const& sprite()const noexcept { return m_sprite; }
	void sprite( Sprite s )noexcept { m_sprite = std::move( s ); }
private:
	Sprite m_sprite;
};

template<typename...Mixins>
class mixin : public Mixins...{};

using Entity = mixin<HasPosition, HasVelocity, HasSprite>;
using Decoration = mixin<HasPosition, HasSprite>;
using InvisibleEntity = mixin<HasPosition, HasVelocity>;

template<typename CompareT, typename...Attribs> struct has_mixin;
template<typename CompareT, typename...Attribs> 
struct has_mixin<CompareT, mixin<Attribs...>> {
	static constexpr bool value = std::disjunction_v<std::is_same<CompareT, Attribs>...>;
};


template<typename...Attribs> struct is_drawable {
	static constexpr bool value = std::conjunction_v<
		has_mixin<HasPosition, Attribs...>,
		has_mixin<HasSprite, Attribs...>
	>;
};

template<typename EntityT>
void draw( EntityT const& entity, Graphics& gfx )noexcept {
	if constexpr( is_drawable<EntityT>::value )
	{
		gfx.DrawSprite( entity.position(), entity.sprite() );
	}
}
After writing this up, I realized that writing a constructor would be difficult if not impossible with this type of mixin implementation.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

Post Reply