Reputation: 1910
Sorry for the title, I couldn't find a more appropriate name. If anyone has a better one I will gladly change it.
Let say we have a set of solid geometry objects:
struct ray { ... };
struct plane { ... };
struct box { .... };
struct sphere { .... };
I'm currently thinking on a templated function for intersection algorithms, so that we can create specialized versions of the function depending on the types to be checked at runtime:
template <typename operand_type1,
typename operand_type2>
RET_DATA check_intersection(operand_type1 operand1, operand_type2 operand2){ ... };
For a ray/shpehre intersection we would provide the following implementation:
template<> RET_DATA check_intersection<ray, sphere>(ray operand1, sphere operand2){ ... };
I left RET_DATA intentionally undefined as depending on each specialization we may want different information to be returned. For example for this case we can return the number of intersections (if any) and the associated points. For sphere-sphere we could return the volume of intersection. The problem is that we also need to create a new (return) type that the user of this template will need to know.
My current best candidate idea is to create a functor and specialize for each type. Inside the functor define a return struct with a common name to each specialization (i.e result):
/** templated functor */
template<typename T1, typename T2> struct check_intersect {
typedef intersection_data<T1, T2> result;
check_intersect(T1 operand1, T2 operand2) : operand1(operand1), operand2(operand2) {}
result operator()(T1 operand1, T2 operand2){ ... }
};
/** tempalted auxiliar return data type */
template<typename T1, typename T2> struct intersection_data { ... }
We would then provide specializations to both depending on the data types. This ends more or less in this usage pattern:
check_intersect<ray, sphere>::result ret = check_intersect<ray, sphere>(my_ray, my_sphere);
The user still has to guess the interface for result although with modern IDEs the problem is somehow reduced. This is what I don't like about it.
Is this approach flawed? Any better design idea to solve the extensibility for upcoming solid-geometry types? Is this the most elegant approach?
Maybe templates are not the right tool for this, I could just provide different functions for each case....
Thank you.
Upvotes: 2
Views: 99
Reputation: 137860
Don't use a template unless there's a general-case implementation. Unless you actually have a function body for template< typename x, typename y > check_intersection( x, y )
, don't declare it.
Use overloading instead of template specialization. Overloading is the tool designed to solve this kind of problem. Specialization is different. It's OK to use specific overloads together with general templates.
Use a base class for the different RET_DATA types. This doesn't need to have a virtual
destructor if you bind to a constant reference:
check_intersect_result const § = check_intersect( my_sphere, my_cone );
This does not slice the return value. The correct destructor will be called even if it's not virtual
. But you can use virtual
if you like.
The issue goes away completely if you use C++11 auto
. Then you don't even need a base class.
auto &§ = check_intersect( my_sphere, my_cone );
This is nice precisely because the user doesn't need to know the return type, to use the function.
Separate plain functions and metafunctions are better than umbrella classes with member operator()
and result_type
. For extensibility, it's easier to add new free functions and metafunction specializations than to modify a monolithic class.
It might not be a bad idea to make an intersection_result
meta-function, e.g. intersection_result< sphere, cone >::type
, but I would avoid requiring it just to call the function.
Upvotes: 2