Click here to Skip to main content
15,885,216 members
Please Sign up or sign in to vote.
4.50/5 (2 votes)
See more:
I'm currently looking for ideas to improve our vector library. In Boost::uBlas I stumbled on a reference to Blitz++, which provdies a very neat looking method for initializing small sized vectors (and matrices):
C++
Array<float,2> A(3,3); //two-dimensional 3-by-3 array of float
A = 1, 2, 3,
    4, 5, 6,
    7, 8, 9;

I couldn't pin down an actual download link for the lib, but after some thought I came up with a way to implement something that supports the above syntax:
C++
#include <iostream>

template <int N> class Tuple; // forward decl
template <int N, int M>
class TupleArgs {
public:
   Tuple<N>* tuple;
   Tuple<M>* uninitialized;
   TupleArgs(Tuple<N>* t, Tuple<M>* u) : tuple(t), uninitialized(u) {}
};

template <int N>
class Tuple {
public:
   double head;
   Tuple<N-1> tail;

   Tuple() : head(0) {}

   TupleArgs<N, N-1> operator=(double a) {
      head = a;
      return TupleArgs<N, N-1>(this, &tail);
   }

   Tuple<N>& operator=(const Tuple<N>& other) {
      head = other.head;
      tail = other.tail;
      return *this;
   }
};
template <>
class Tuple<1> {
public:
   Tuple<1>() : head(0) {}
   Tuple<1>(double a) : head(a) {}
   Tuple<1>& operator=(const Tuple<1>& other) {
      head = other.head;
      return *this;
   }
   double head;
};
template <int N, int M>
TupleArgs<N, M-1> operator,(TupleArgs<N, M>& a, double b) {
   a.uninitialized->head = b;
   return TupleArgs<N, M-1>(a.tuple, &a.uninitialized->tail);
}
template <int N>
Tuple<N>& operator,(TupleArgs<N,1>& a, double b) {
   a.uninitialized->head = b;
   return *a.tuple;
}

int _tmain(int argc, _TCHAR* argv[])
{
   Tuple<3> triple;
   triple = 4, 5, 6;
   std::cout << triple.head << ' ' << triple.tail.head << ' ' << triple.tail.tail.head << ' ' << std::endl;
   Tuple<9> matrix33;
   matrix33 = 1, 2, 0,
              4, 5, 3,
              7, 8, 2;
   // use your debugger if you want to check that this really works
   return 0;
}

Unfortunately, while I've found not only a way to use this kind of initialization syntax but did so for arbitrary (fixed) size arrays, all this comes at a cost:

1. overriding the comma operator destroys it's usual behaviour of respecting (left to right) evaluation order: the expressions in the initializer list will be evaluated in arbitrary order
2. I had to provide an implicitely usable constructor taking a double value, and that allows the compiler to implicitely convert variables or expressions of type double to Tuple. If someone accidentally uses a double when he intended a Tuple, that mistake won't be detected, and may be hard to detect later. Effectively, the C++ type safety is compromised.
3. Since the comma operator has extremely low priority (lower than assignment), intializing an array with an expression requires enclosing it in parenthesis. Failure to do so can break the code in many unforseeable ways. And potentially the break will be silent (i. e. the compiler may not be able to point out the problem).
4. The Tuple assignment operator cannot be chained directly: the values of the above assignment expressions are indeed of the correct type (Tuple), but the only way to chain the assignment is by enclosing the entire assignment in brackets:
C++
Tuple<3> triple1, triple2;
triple1 = triple2 = 1, 2, 3; // error: the left assignment tries to assign the
                             // result of (triple2 =1), which has the wrong type
triple1 = (triple2 = 1, 2, 3); // ok, but ugly(


My question:
Is it possible to avoid most of the drawbacks outlined above? I realize that item 1 can't be helped, but I wonder about the other three...

P.S.:
1. I've delved in the boost libraries before, but given their contrived syntax I haven't (yet) found the heart to check out Boost::Spirit, which also uses this kind of initialization syntax. If someone knows Boost::Spirit well enough to understand the mechanics used there, maybe he can point me to the relevant articles/bits of documentation/whatever I'd need to know to improve the above.
2. As mentioned above, I've found the Blitz++ documentation. But I couldn't pinpoint a working download link for the actual ibrary. It seems that it's no longer supported, but I wonder if the original source code is still available somewhere?

P.P.S.: sorry for the long question, but I believe all this information is required to point out what I already know - and anyway this forum is called quick answers, not quick questions ;)
Posted
Comments
Matthew Faithfull 14-Mar-13 6:22am    
It would be interesting to know which Compiler and version you're using as my understanding is that any deviation in the evaluation order by overriding the comma operator would be a standards breaking compiler bug. The only legitimate puporse of overriding the comma operator I've ever come across before was to gaurantee left to right evaluation in the face of aggressive optimisation.
Stefan_Lang 14-Mar-13 8:36am    
(moved from accidental post as solution:)
The evaluation order is the least of my problems, but one that is indeed compiler-dependend. The standard says that the (built-in) comma operator must evaluate each operand left to right. But it also says that there is no guarantee for the order of evaluation of function arguments. By providing an overload function, the operands are now treated as function arguments, rather than a comma-separated sequence.

I actually expect most compilers will still evaluate all operands in correct order, but the standard doesn't enforce it. This has been discussed at stackoverflow and various other locations.
Stefan_Lang 14-Mar-13 8:43am    
Thanks for the link to Blitz++.

On a sidenote I just realized I accidentally posted my response as a solution. I deleted it and reposted my response correctly, but along with the solution I obliterated your response. For those who'd also want to take a look at Blitz, the link is http://sourceforge.net/projects/blitz/

Don't do it... As you have noticed, it come with a lot of drawback.

If it was fully supported, I could recommand you to use initializer list (C++ 11 feature) but support is still marginal in Visual Studio 2012.

Alternatively, you might want to uses struct (without constructor) if you are using and older compiler.
 
Share this answer
 
Comments
Stefan_Lang 20-Mar-13 4:48am    
Thanks for your feedback. I've looked at initializer lists, but unfortunately we use VS 2010 and are not going to upgrade anytime soon.

Hmm, that makes me wonder if I could trick the compiler into treating a tuple-like class as a standard array and get away with that. C99 array initialization is very restrictive however, and I don't think you can pass an initializer list as an argument to an operator overload. :-(
Stefan_Lang 21-Mar-13 12:05pm    
I'm going to accept this solution as answer. Not because it answers the question, but because I agree that the drawbacks are not worth it.

Thank you anyway for looking into my request.
Comma operator overloading is a cosmic feature.

Here more a suggestion than a solution:

C++
#include <iostream>

template <class T, int N> class Tuple; // forward decl
template <class T, int N, int M>
class TupleArgs {
public:
   Tuple<T, N>* tuple;
   Tuple<T, M>* uninitialized;
   TupleArgs(Tuple<T, N>* t, Tuple<T, M>* u) : tuple(t), uninitialized(u) {}
};

template <class T, int N>
class Tuple {
public:
   T head;
   Tuple<T, N-1> tail;

   Tuple() : head(0) {}

   TupleArgs<T, N, N-1> operator=(T a) {
      head = a;
      return TupleArgs<T, N, N-1>(this, &tail);
   }

   Tuple<T, N>& operator=(const Tuple<T, N>& other) {
      head = other.head;
      tail = other.tail;
      return *this;
   }
};

template <class T>
class Tuple<T, 1> {
public:
   Tuple<T, 1>() : head(0) {}
   Tuple<T, 1>(T a) : head(a) {}
   Tuple<T, 1>& operator=(const Tuple<T, 1>& other) {
      head = other.head;
      return *this;
   }
   T head;
};

template <class T, int N, int M>
TupleArgs<T, N, M-1> operator,(TupleArgs<T, N, M>& a, T b) {
   a.uninitialized->head = b;
   return TupleArgs<T, N, M-1>(a.tuple, &a.uninitialized->tail);
}
template <class T, int N>
Tuple<T, N>& operator,(TupleArgs<T, N, 1>& a, T b) {
   a.uninitialized->head = b;
   return *a.tuple;
}

int _tmain(int argc, _TCHAR* argv[])
{
   Tuple<int, 3> triple;
   triple = 4, 5, 6;
   std::cout << triple.head << ' ' << triple.tail.head << ' ' << triple.tail.tail.head << ' ' << std::endl;
   Tuple<int, 9> matrix33;
   matrix33 = 1, 2, 0,
              4, 5, 3,
              7, 8, 2;
   // use your debugger if you want to check that this really works

   Tuple<int, 3> triple1, triple2;
   //triple1 = triple2 = 1, 2, 3; // error: the left assignment tries to assign the
                                // result of (triple2 =1), which has the wrong type
   //triple1 = (triple2 = 1, 2, 3); // ok, but ugly(

   triple1 = 1, 2, 3;
   triple2 = triple1;

   return 0;
}


With int as T it works fine, with double as T it doesn't work.

Perhaps the upper mentioned compiler bug?

My environment : VS2010, SP1Rel

Best regards
 
Share this answer
 
Comments
Stefan_Lang 15-Mar-13 9:33am    
I also have VS 2010, and my code works (for double). As far as i can see you added the basic number type to the template argument list, and then added a partial specialization for Tuple<T,1> . That isn't quite what my concerns were about: I was worried over stuff like this:

Tuple<3> aa, bb;
double a;
// do some stuff ...
bb = a; // typo. the intention was bb=aa !

In the meantime I've digged into Blitz++ and found that it suffers from all the problems mentioned above, although it considers the case above legit in the sense that an incomplete initialization list will just initialize the remaining array components with the last provided value. This behaviour is consistent with array initialization, so I suppose that's not a bad way to handle this case.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900