tags:

views:

176

answers:

6

Hi,
I want to create my own Point struct it is only for purposes of learning C++.
I have the following code:

template <int dims, typename T>
struct Point {
    T X[dims];
    Point(){}
    Point( T X0, T X1 ) {
        X[0] = X0;
        X[1] = X1;
    }
    Point( T X0, T X1, T X2 ) {
        X[0] = X0;
        X[1] = X1;
        X[2] = X2;
    }
    Point<dims, int> toint() {
        //how to distinguish between 2D and 3D ???
        Point<dims, int> ret = Point<dims, int>( (int)X[0], (int)X[1]);
        return ret;
    }
    std::string str(){
        //how to distinguish between 2D and 3D ???
        std::stringstream s;
        s << "{ X0: " << X[0] << " | X1: " <<  X[1] << " }";
        return s.str();
    }
};
int main(void) {

    Point<2, double> p2d = Point<2, double>( 12.3, 45.6 );
    Point<3, double> p3d = Point<3, double>( 12.3, 45.6, 78.9 );

    Point<2, int> p2i = p2d.toint(); //OK
    Point<3, int> p3i = p3d.toint(); //m???

    std::cout << p2d.str() << std::endl; //OK
    std::cout << p3d.str() << std::endl; //m???
    std::cout << p2i.str() << std::endl; //m???
    std::cout << p3i.str() << std::endl; //m???

    char c; std::cin >> c;
    return 0;
}

of couse until now the output is not what I want. my questions is: how to take care of the dimensions of the Point (2D or 3D) in member functions of the Point?

many thanks in advance
Oops

+1  A: 

Add a member variable:

template <int dims, typename T>
struct Point {
    //whatever
    static const int Dimensions = dims;
};

now you can use it both in the member functions and in the outer code (if you don't change its access restrictions of course).

sharptooth
Remember to define it later, in namespace scope.
Potatoswatter
What's the point? In non-templated outer code, I already know that `Point<2, int>::Dimensions` is 2. And in templated outer code using `Point<N, int>`, I have N. There might be cases I didn't consider, but please add an example when you need this.
MSalters
@MSalters: Looks like you're right, but this is how I'd address the problem.
sharptooth
+6  A: 

Your dimensions are fixed at compile time, by the template arguemnt dims, so you can iterate over them:

std::string str(){
    //how to distinguish between 2D and 3D ???
    std::stringstream s;
    s << "{ ";
    std::copy( X, X+dims, std::ostream_iterator<T>( s, "|") );
    s << " }";
    return s.str();    
}

Also, you can provide a constructor in terms of the dims:

Point( const T (&c) [dims] ){
  std::copy( c, c+dims, X );
}

This allows for code like double[] d={1,2,3}; Point<3,double> p(d);, which puts the decision on the number of arguments elsewhere.

For the 'cast-to-point-of-ints' constructor you just can use Point as the argument:

Point<dims,int> toint() const {
    Point<dims,int> result;
    std::copy( X, X+dims, result.X );
    return result;
}
xtofl
Potatoswatter
@Potatoswatter: thanks. It's a big shortcoming that you can't assign a fixed-size array to another one...
xtofl
+1  A: 

If you want your member methods to know the dimension (your "dims" template parameter), add the following line to your template class:

static const int Dimension = dims;

then use Dimension in your methods. This is a static const member variable, so the compiler will be able to optimize it away and not require any storage for it.

I just noticed that you can also use the identifier "dims" directly in a member function:

public void PrintDimensions()
{ 
     std::cout << dims;
}

works fine with my compiler.

John Källén
Yes, there's no actual need for the static variable, because the value is already encoded in the type.
visitor
Any template parameter, type or non-type, can be used _almost anywhere_ inside a class template - whether in the base declarations, member list, or method implementations. `dims` is a non-type parameter. To be very precise, it's an Integral Constant Expression. That does mean you can't use it on the left side of an assignment, hence _almost_ anywhere.
MSalters
+3  A: 

Instead of hard-coding the assignments, you should simply use a loop. For example:

std::string str(){
    if ( dims > 0 ){
        std::stringstream s;
        s << "{ X0: " << X[0];
        for ( int i = 1; i < dims; i++ ){
            s << " | X" << i << ": " <<  X[i];
        }
        s <<" }";
        return s.str();
    }
    return "{}";
}

In terms of your constructor, though, you'll have to either specialize your template to have contructors with the correct number of parameters, or you will have to wait for C++0x to use initializer lists, or you will simply have to settle for a somewhat less powerful syntax, where you pass in an initializing array. For all the other functions, though, you can just use a for-loop, using dims for the dimensions of the points. I should also point out that boost::array class may already provide the functionality you need and, if not, it might be advisable to build whatever functionality you need on top of that class as it is very similar to what you have.

Also, as an aside, you should provide a typical ostream operator<< function for your class, and optionally implement the str() function on top of that, since you already effectively implement it in your implementation of str(), but providing that operator overload is more flexible and powerful than simply providing str(). You might also be interested in boost::lexical_cast, as it can stringify any object providing the ostream operator.

Michael Aaron Safyan
+2  A: 

As others have said, how to distinguish between 2D and 3D? is answered by simply using dims as a variable.

The more interesting question is how to get the constructors to behave such that you can only call the correct one. One alternative is boost::enable_if, which generates a purposeful compiler error to essentially cancel a function declaration if a condition isn't met.

template <int dims, typename T>
struct Point {
    T X[dims];
    Point(){} // should this really be empty?
    Point( typename enable_if_c< dims == 2, T >::type X0, T X1 ) {
        X[0] = X0;
        X[1] = X1;
    }
    Point( typename enable_if_c< dims == 3, T >::type X0, T X1, T X2 ) {
        X[0] = X0;
        X[1] = X1;
        X[2] = X2;
    }

This technique is usually called SFINAE, for Substitution Failure Is Not An Error (subtext: the compiler may try another overload), but in this case, we do actually want the error.

Another alternative is to provide a variable type and default argument to selectively disable the third argument, or force it to be specified:

template< int dims, typename T >
class tweak_point_ctor_argument; // disallow general case of dims != 2,3

template< typename T >
class tweak_point_ctor_argument< 2, T > {
    tweak_point_ctor_argument() {} // private constructor
    typedef tweak_point_ctor_argument type;
    operator int() { return 0; }
    friend struct Point;
};

template< typename T >
class tweak_point_ctor_argument< 3, T > {
    typedef T type;
    friend struct Point;
};

    …
    Point( T X0, T X1, 
        typename tweak_point_ctor_argument< dims, T >::type X2
            = tweak_point_ctor_argument<dims, T>() ) {
        X[0] = X0;
        X[1] = X1;
        X[2] = X2;
    }

If dims is 2, ::type evaluates to tweak_point_ctor_argument< 2, T >, which cannot be constructed except by the default argument. So the user cannot possibly call the constructor with a third parameter. If dims is 3, ::type evaluates to T, causing the default argument initializer to be invalid, forcing the user not to use it.

I haven't tested this code, it's just a demonstration of the various ways you can solve a problem, enable_if is the preferable method here.

Potatoswatter
+1  A: 

How about having one class for Point2D and one for Point3D and wrap those classes into Point class?

struct nil;

class PointBase {
 /* ... */
 PointBase();
 PointBase(const PointBase&);
 virtual ~PointBase();
}

template<class T_arg1, class T_arg2>
class Point2D : public PointBase {
 /* ... */
 Point2D(T_arg1 arg1, T_arg2 arg);
 Point2D(const Point2D& src) : PointBase(src) {}
 virtual ~Point3D();
};


template<class T_arg1, class T_arg2, class T_arg3>
class Point3D : public PointBase {
 /* ... */
 Point3D(T_arg1 arg1, T_arg2 arg2, T_arg3 arg3);
 Point3D(const Point3D& src) : PointBase(src) {}
 virtual ~Point3D();
};

template<class T_arg1, class T_arg2, class T_arg3 = nil>
class Point : public Point3D<T_arg1, T_arg2, T_arg3> {
 /* ... */
 Point(T_arg1 arg1, T_arg2 arg2, T_arg3 arg3)
  : Point3D<T_arg1, T_arg2, T_arg3>(arg1, arg2, arg3) {} 
 Point(const Point& src) : Point3D<T_arg1, T_arg2, T_arg3>(src) {}
 virtual ~Point();
}

template<class T_arg1, class T_arg2>
class Point<T_arg1, T_arg2, nil> : public Point2D<T_arg1, T_arg2> {
 /* ... */
 Point(T_arg1 arg1, T_arg2 arg2)
  : Point2D<T_arg1, T_arg2>(arg1, arg2) {} 
 Point(const Point& src) : Point2D<T_arg1, T_arg2>(src) {}
 virtual ~Point();
}

and then use:

Point<double, double> my2DPoint(0.0, 0.0);
Point<double, double, double> my3DPoint(0.0, 0.0, 0.0);
Willy