[C++] Using friend classes to maintain compile-time invariant

Started by
0 comments, last by civguy 19 years, 7 months ago
Sorry for the long title. I have a situation where I want to maintain a strict compile-time invariant such that a compile-time error results if the invariant is not met. I have an object templated on an integer to represent its dimension. The object must be initialised with exactly the same number of integers as its dimensionality, i.e Object<1> must be initialised with exactly one integer and Object<3> must be initialised with exactly three integers. The mechanism I would like to use to achieve this is repeated application of operator[]. So application of operator[] with an integer parameter to a base object will create an Object<1>. Application of operator[] with an integer parameter to an Object<1> will create an Object<2> and so on. It must not be possible for the user to create an Object in any other way, as this could break the invariant without being detectable at compile-time. The code I have come up with uses the following pattern:
class A
{

	public:

		static const A a;

		template <int X>
		class B
		{

			friend class A;
			friend class B<X - 1>;

			public:

				const B<X + 1> operator[](int i) const
				{
					return B<X + 1>(i_ + i);
				}

			private:

				B(int i) : i_(i * X) {}

				int i_;

		};

		const B<1> operator[](int i) const
		{
			return B<1>(i);
		}

	private:

		A(){}

};

int main()
{
	A::B<2> r = A::a[0][0];
}

A few notes about the code: Two classes are used instead of one to simplify usage of the base object. It is simply A::a, instead of A<0>::a, as it would be if I had used one class specialised for the base case. The invariant being maintained here is simply an example. For the real code I actually need to maintain a list of integers. I have left the copy-constructors undefined here so the default public versions will be used. I don't see any real downsides to allowing the user to copy the existing objects as long as the only way to modify/create a new object is through operator[]. The issue I have is the usage of the two friend declarations. I dislike using the friend classes because the friend keyword is an all-or-nothing approach and therefore breaks encapsulation. Is it justified for this situation, or is there a better way to do what I want to do? I have deliberately restricted this post to this specific solution to my problem. I am well aware that there may be better solutions that do not involve using repeated application of operator[] to construct objects, but that is something I am considering seperately. At the moment I wish to get the best version of this type of code so that I can evaluate it's suitability for my problem against other options. Thanks for your time, Enigma EDIT: Sorry, broke the formatting horribly on my first attempt! EDIT2: Fixed title - not sure what a complile-time invariant is!
Advertisement
Aren't you a bit too extreme here? These are A's internals after all, it's not like some criminal mistermind could use your code for pure evil with they way you've done it. Boost multiarray uses similar initializers, and they're pure public (EVIL!) so there's no need for friends (it's slightly different though, an initializer class similar to A::B is used as a parameter in constructor of another class). You can think of the initializer class as a POD type; it just contains the initialization data and gives users a "convenient" way to set it. To be honest, I laughed when I read your post, but don't take it too personally.

This topic is closed to new replies.

Advertisement