Subscribe to MentalFloss Minutes        RSS Feed
-----

C++ Enum Flip Flop OR Junk Fields?

Icon Leave Comment
Disclaimer: This is probably bad code, but I came across this in trying to solve a problem, and I want to share. Feel free to offer suggestions for improvement.

#include <iostream>

using namespace std;

typedef enum
{
	A,
	B,
	C,
	D,
	E,
	F,
	G,
	ERROR

} BroadType;

typedef enum
{
	TYPE_A,
	TYPE_B,
	TYPE_E,
	TYPE_ERROR

} NarrowType;

struct Data
{
	NarrowType type;
	
	Data(BroadType type)
	{
		// We expect only A,B,E types to be used here.
		if(type == A)
			this->type = TYPE_A;
		else if(type == B)
			this->type = TYPE_B;
		else if(type == E)
			this->type = TYPE_E;
		else
			this->type = TYPE_ERROR;
	}
	
	void print_type()
	{
		if(type == TYPE_A) cout << "TYPE_A" << endl;
		else if(type == TYPE_B) cout << "TYPE_B" << endl;
		else if(type == TYPE_E) cout << "TYPE_E" << endl;
		else cout << "TYPE_ERROR" << endl;
	}
};

int main()
{
	Data d1(A);
	d1.print_type();
}



So, here's the idea: An object maintains a piece of information, but the provided information is in a different format from what it wants to use. You can note that there are two separate enums, and while they conceptually have some overlap, they are entirely disjoint. We know that the object supports A,B,E types but nothing else.

We see that upon construction, the type is converted to a type it can use.

One very important detail: We do not store the original type.

Problem: What if we want a copy?

Let's extract the convert code to a function.

struct Data
{
	NarrowType type;
	
	Data(BroadType type)
	{
		// We expect only A,B,E types to be used here.
		this->type = get_type(type);
	}
	
	NarrowType get_type(BroadType type)
	{
		if(type == A)
			return TYPE_A;
		else if(type == B)
			return TYPE_B;
		else if(type == E)
			return TYPE_E;
		else
			return TYPE_ERROR;
	}
	
	void print_type()
	{
		if(type == TYPE_A) cout << "TYPE_A" << endl;
		else if(type == TYPE_B) cout << "TYPE_B" << endl;
		else if(type == TYPE_E) cout << "TYPE_E" << endl;
		else cout << "TYPE_ERROR" << endl;
	}
};



Let's overload the get_type function to also convert NarrowTypes to BroadTypes.

struct Data
{
	NarrowType type;
	
	Data(BroadType type)
	{
		// We expect only A,B,E types to be used here.
		this->type = get_type(type);
	}
	
	NarrowType get_type(BroadType type)
	{
		if(type == A)
			return TYPE_A;
		else if(type == B)
			return TYPE_B;
		else if(type == E)
			return TYPE_E;
		else
			return TYPE_ERROR;
	}
	
	BroadType get_type(NarrowType type)
	{
		if(type == TYPE_A)
			return A;
		else if(type == TYPE_B)
			return B;
		else if(type == TYPE_E)
			return E;
		else
			return ERROR;
	}
	
	void print_type()
	{
		if(type == TYPE_A) cout << "TYPE_A" << endl;
		else if(type == TYPE_B) cout << "TYPE_B" << endl;
		else if(type == TYPE_E) cout << "TYPE_E" << endl;
		else cout << "TYPE_ERROR" << endl;
	}
};



And now let's write the copy code to return a new copy of data.

#include <iostream>

using namespace std;

typedef enum
{
	A,
	B,
	C,
	D,
	E,
	F,
	G,
	ERROR

} BroadType;

typedef enum
{
	TYPE_A,
	TYPE_B,
	TYPE_E,
	TYPE_ERROR

} NarrowType;

struct Data
{
	NarrowType type;
	
	Data(BroadType type)
	{
		// We expect only A,B,E types to be used here.
		this->type = get_type(type);
	}
	
	NarrowType get_type(BroadType type)
	{
		if(type == A)
			return TYPE_A;
		else if(type == B)
			return TYPE_B;
		else if(type == E)
			return TYPE_E;
		else
			return TYPE_ERROR;
	}
	
	BroadType get_type(NarrowType type)
	{
		if(type == TYPE_A)
			return A;
		else if(type == TYPE_B)
			return B;
		else if(type == TYPE_E)
			return E;
		else
			return ERROR;
	}
	
	void print_type()
	{
		if(type == TYPE_A) cout << "TYPE_A" << endl;
		else if(type == TYPE_B) cout << "TYPE_B" << endl;
		else if(type == TYPE_E) cout << "TYPE_E" << endl;
		else cout << "TYPE_ERROR" << endl;
	}
	
	Data copy()
	{
		return Data(get_type(type));
	}
};

int main()
{
	Data d1(A);
	d1.print_type();
	
	Data d2 = d1.copy();
	d2.print_type();
}



What would this look like if we just stored the BroadType too?

struct Data
{
	NarrowType type;
	BroadType lookup;
	
	Data(BroadType type)
	{
		// We expect only A,B,E types to be used here.
		this->lookup = type;
		this->type = get_type(type);
	}
	
	NarrowType get_type(BroadType type)
	{
		if(type == A)
			return TYPE_A;
		else if(type == B)
			return TYPE_B;
		else if(type == E)
			return TYPE_E;
		else
			return TYPE_ERROR;
	}
	
	void print_type()
	{
		if(type == TYPE_A) cout << "TYPE_A" << endl;
		else if(type == TYPE_B) cout << "TYPE_B" << endl;
		else if(type == TYPE_E) cout << "TYPE_E" << endl;
		else cout << "TYPE_ERROR" << endl;
	}
	
	Data copy()
	{
		return Data(lookup);
	}
};



Maybe that's the correct decision. Even if we constructor chain we still need to create the function to flip back.

struct Data
{
	NarrowType type;
	
	Data(BroadType type) : Data(get_type(type)) 
	{
	}
	
	Data(NarrowType type)
	{
		// We expect only A,B,E types to be used here.
		this->type = type;
	}
	
	NarrowType get_type(BroadType type)
	{
		if(type == A)
			return TYPE_A;
		else if(type == B)
			return TYPE_B;
		else if(type == E)
			return TYPE_E;
		else
			return TYPE_ERROR;
	}
	
	BroadType get_type(NarrowType type)
	{
		if(type == TYPE_A)
			return A;
		else if(type == TYPE_B)
			return B;
		else if(type == TYPE_E)
			return E;
		else
			return ERROR;
	}
	
	void print_type()
	{
		if(type == TYPE_A) cout << "TYPE_A" << endl;
		else if(type == TYPE_B) cout << "TYPE_B" << endl;
		else if(type == TYPE_E) cout << "TYPE_E" << endl;
		else cout << "TYPE_ERROR" << endl;
	}
	
	Data copy()
	{
		return Data(type);
	}
};



Ultimately, it seems like a choice needs to be made. Do you store junk fields or do you flip between values. Neither choice seems good, but one seems necessary if requirements are to be met. If we store junk fields, then that implies the object relies on the information in some way when it really doesn't. If we flip between values, we now have two functions instead of one. We add to what needs maintained and updated. Maybe the problem's small here, but I'm interested in building mental paradigms and solutions generally.

Perhaps an argument will be: I wouldn't create two enums like this, so it wouldn't be a problem, but this was a forced decision, so it can definitely happen.

Also, don't get bogged down in the variable names. I need to abstract away the details of what I'm working on, so I can't give exact specifics.

0 Comments On This Entry

 

November 2018

S M T W T F S
    123
45678910
11121314151617
18192021 22 2324
252627282930 

Tags

    Recent Entries

    Recent Comments

    Search My Blog

    0 user(s) viewing

    0 Guests
    0 member(s)
    0 anonymous member(s)