Jump to content
  • Advertisement
Sign in to follow this  
ToohrVyk

Factoring

This topic is 4157 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I'm writing a small library right now. The objective is to allow the user to manipulate objects which are a function of time transparently. For instance, I would write:
time::value<double> polynomial = 2.0 * time::t * time::t + 3.0 * time::t + 1.0;
time::value<double> linear = 3.0 * polynomial + 2.0;
std::cout << linear(time::now) << std::endl;
This library works based on expression templates. This requires me to define, for every operator, three versions: two with a single time-based member, and one with two time-based members. The code for all of them is extremely similar: the differences have been underlined:
template<typename T,typename LI,typename RI>
struct Sum
{
  LI l;
  RI r;
  Sum(const LI & l, const RI & r) : l(l), r(r) { }
  T eval(time t) { return l.eval(t) + r.eval(t); }
};

template<typename RI, typename T>
Wrap< Sum<T,Const<T>,RI>,T> 
operator+(const T & left, const Wrap<RI,T> & right) 
{ return Sum<T,Const<T>,RI>(left,right.impl); }

template<typename LI, typename T>
Wrap< Sum<T,LI,Const<T> >,T>
operator+(const Wrap<LI,T> & left, const T & right)
{ return Sum<T,LI,Const<T> >(left.impl,right); }

template<typename LI, typename RI, typename T>
Wrap< Sum<LI,RI>,T>
operator+(const Wrap<LI,T> & left, const Wrap<RI,T> & right)
{ return Sum<T,LI,RI>(left.impl,right.impl); }
So, first question: how to reduce the amount of repetitiveness, in order to generate all operators with as little code as possible? Second question: how do I handle operations which have different argument types (such as scalar * vector) ?

Share this post


Link to post
Share on other sites
Advertisement
I'm no expert on expression templates, so I won't speak directly. But I believe it was Jyk who had created a math library based on template expressions. Hopefully he'll be along, or perhaps you could PM him and ask him to reply.

Share this post


Link to post
Share on other sites
Maybe this is speaking to my own ignorance, but is there a particular reason you have to take the right side argument by reference? Are you expecting to use complex types with this? Barring that, would it be too cumbersome for the user to create a temporary variable? There are a lot libraries that require this kind of behavior (glLightfv, for instance). The point being that you're already going to have to declare each operator twice (operator+ and then operator+=) so why make it six times? Either there's no reason to have two versions with one variable and one with two variables, or you're stuck with some amount of redundancy.

Even if you boil it down to a very clever common factor, you're still going to have to implement each one's unique portions individually. Is the benefit of having a common factor more than the cost in effort and readability?

Even still, I wouldn't have Sum<type1,type2> along with Sum<type>. It seems inconsistent.

As far as #2, I wouldn't worry about type safety; it will add too much bloat to your function, and templates tend to be bad enough. If the programmer doesn't know better than to try Divide<ComplexNumber,Grapefruit>, then they should be hit over the head with a brick anyways.

Share this post


Link to post
Share on other sites
This seems like a job for Boost.PP.

I can't see how you could reduce the code required for the specialisations.

Share this post


Link to post
Share on other sites
Quote:
Original post by erissian
Maybe this is speaking to my own ignorance, but is there a particular reason you have to take the right side argument by reference?


I see no reason not to take it by constant reference...

Quote:
The point being that you're already going to have to declare each operator twice (operator+ and then operator+=) so why make it six times? Either there's no reason to have two versions with one variable and one with two variables, or you're stuck with some amount of redundancy.


Actually, that would be only three times each (W op T, T op W, W op W) for binary operators without side-effects, which can then be used to implement each side-effect operator in one strike only.

Quote:
Even if you boil it down to a very clever common factor, you're still going to have to implement each one's unique portions individually. Is the benefit of having a common factor more than the cost in effort and readability?


Ideally, the library would be used, not read. So, common-factor code such as the following would certainly not bother me:
showOperators "timefunc.hpp" {
binary = [
("*","mul"); ("+","add"); ("/","div"); ("-","sub");
("<<","shl"); (">>","shr"); ("%","mod") ];
compare = [
("==","eq"); ("<","lt"); (">","gt"); ("<=","le");
(">=","ge"); ("!=","ne") ];
unary = [
("!","neg"); ("*","deref") ]
}



Quote:
Even still, I wouldn't have Sum<type1,type2> along with Sum<type>. It seems inconsistent.


I would be using Sum<LeftImpl,RightImpl,ReturnType> anyway.

Quote:
As far as #2, I wouldn't worry about type safety; it will add too much bloat to your function, and templates tend to be bad enough. If the programmer doesn't know better than to try Divide<ComplexNumber,Grapefruit>, then they should be hit over the head with a brick anyways.


It's not about type safety. It's about operator*(A,B) returning either type A or type B depending on which of A and B is the scalar or vector type. I have to somehow know what the return type is.

Share this post


Link to post
Share on other sites
Quote:
So, first question: how to reduce the amount of repetitiveness, in order to generate all operators with as little code as possible?


I would use a local macro for this, just #define it for the scope of the decelerations taking two parameters and #undef it at the end, you will have to be careful with operator, though since passing commas to macros is at best unwieldy. (On a side note I believe this is the approach taken by boost.lambda)

Quote:
Second question: how do I handle operations which have different argument types (such as scalar * vector) ?


If its an option then I would use boost::typeof otherwise you could use try using boost::result_of or something similar but it relies on the user following certain conventions.

boost::typeof example:

template<typename L, typename R>
Wrap
< BOOST_TYPEOF_TPL(*reinterpret_cast<L*>(0) * *reinterpret_cast<R*>(0))
, L
, R
>
operator*(L lhs, R rhs)
{
// ...
}

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!