C++11 auto and promoted types

Post bugs here (at least until we get a more formal bug-tracking system set up).

C++11 auto and promoted types

Postby sprite » Fri Apr 25, 2014 8:41 pm

I've been updating my code base to use some C++11 features such as auto, but CML managed to cause me a serious headache, as shown by the following code:

Code: Select all

#include <cml/cml.h>

int main()
{
   cml::matrix44f_c modelMatrix = cml::identity_4x4();
   cml::matrix44f_c viewMatrix = cml::identity_4x4();
   cml::matrix44f_c projectionMatrix = cml::identity_4x4();

   cml::matrix44f_c mvpMatrix = projectionMatrix * viewMatrix * modelMatrix;

   std::cout << typeid(mvpMatrix).name() << std::endl;
   //class cml::matrix<float,struct cml::fixed<4,4>,struct cml::col_basis,struct cml::col_major>
      
   auto mvpMatrix2 = projectionMatrix * viewMatrix * modelMatrix;

   std::cout << typeid(mvpMatrix2).name() << std::endl;
   //class cml::matrix<float,struct cml::fixed<4,4>,struct cml::col_basis,struct cml::row_major> // WTF???
}



When specifying the variable type, it's column major. When I use auto, it mysteriously changes to row major!!!

CML apparently uses a default value for the layout of the return type of operator*. It's possible to change that by #define ing CML_DEFAULT_ARRAY_LAYOUT, but I think personally I'd rather it auto deduced if the two arguments were the same layout, and fail otherwise. There's also CML_ALWAYS_PROMOTE_TO_DEFAULT_LAYOUT, but that's defined in CML's defaults.h (so I can't #define something to change it in my own code), and when undefined, it still uses the default if the two sides don't match.

I'm not sure how much (if any) of this is technically a bug, but I do feel like the auto vs explicit variable names having different types shouldn't happen "by default" though.

It certainly took me a long time to figure out why everything suddenly rendered wonky.
sprite
 
Posts: 6
Joined: Tue Jun 29, 2010 10:36 pm

Re: C++11 auto and promoted types

Postby Jesse Anders » Thu May 08, 2014 3:10 am

I realize this post was a while ago (my email notifications got turned off somehow). Sorry for the delay, and for the inconvenience this particular issue caused. I've made a note of the issue for future reference (I gather you were able to work around the issue).
Jesse Anders
Site Admin
 
Posts: 313
Joined: Thu Jan 01, 1970 12:00 am

Re: C++11 auto and promoted types

Postby dnave » Wed Nov 05, 2014 3:17 am

Wow, this is an old post... and yep, my notifications were off too.

I am currently re-implementing CML for C++11, to be named CML2. Better type deduction will be among the improvements, which should fix that odd error (as well as eliminate a bunch of those #define's). If you care to, you can monitor the progress via the Subversion repository at SourceForge:

http://sourceforge.net/p/cmldev/code/HEAD/tree/branches/cml2/

And sorry for the headaches!
Cheers,
Demian
dnave
 
Posts: 113
Joined: Fri Jun 29, 2007 12:46 am
Location: Pittsburgh, PA

Re: C++11 auto and promoted types

Postby sprite » Sat Nov 29, 2014 12:20 pm

Hi there. I've run into a few more issues with auto since then, to do with the expression templates.

With pre C++11 code, I'd explicitly mention a variable type for an expression:

Code: Select all
cml::vector3f result = a + b; // add two vector3fs.


Changing the result type to auto, however, causes the type of result to be an expression template instead of cml::vector3f. This will compile fine, but won't work in all cases.

Code: Select all
auto result = a + b; // a + b isn't actually calculated here!


This can cause problems if I need to change a or b before using result, since the value of result (when it's eventually calculated) will then be different.

Do you have any suggestions as far as best practices go? So far, it seems like the best way is to avoid using auto with cml. Possibly I just need to be rather more careful when writing maths code. At the same time though, since expression templates are an optimisation, I'd probably rather have them turned off unless I explicitly need them.

How is CML2 handling it?
sprite
 
Posts: 6
Joined: Tue Jun 29, 2010 10:36 pm

Re: C++11 auto and promoted types

Postby dnave » Mon Dec 01, 2014 5:50 pm

Howdy, thanks for the update, and I apologize for the delay in responding. The answer has multiple parts, so here we go:

1. I should have pointed out that CML1 should not be used with auto. Because of the way CML1 expression templates are built (internally storing expiring references to sub-expressions), auto is unsafe to use except in particular circumstances.

2. I've completely revamped expression templates in CML2, so you can safely use them with auto.

3. There is no way to automatically coerce an expression into a data-holding type---you have to tell the CML what data type you want. So, if you want a vector3f, you need to specify vector3f as the result. This is not an arbitrary design decision, it is actually fundamental to how the CML works. Consider "auto c = a+b", where a is a vector3f and b is a vectorf (a resizable vector). There is no way for the CML to know if you want a vector3f result or a vectorf result, so instead it returns c as a type that looks like a read-only, fixed-length vector of length 3, but having elements that are evaluated "lazily". The only operation (in CML2) that x does not support is "x.data()", since it doesn't have any data for you to get at! If you need to access data as a pointer, then you need "vector3f x = a+b" to ensure that "a+b" is evaluated and stored to memory. You can then call "x.data()" to send the pointer to OpenGL, for example.

4. As a corollary, and assuming CML2, you can change a and/or b at any time prior to evaluating c. However, as soon as you need access to data, you will need to do something like "vector3f data = c". So, it's better just to do the assignment to vector3f up front, and modify a and/or b prior to the assignment statement.

5. There is no way to disable the expression template implementation, since it's at the core of the CML. I could add a compile-time option to force CML2 to always return a data-holding type as the result of an expression, but my first intuition is that the resulting code would be a mess. It probably could be done in a limited way by overloading operators for specific vector, matrix, and quaternion types (unary and binary + and -, and scalar * and /), but it would be really low on my list of priorities. Fortunately, I don't think the implementation is all that complex, and it really could be done in a user's code instead of the CML. If you want to try it yourself, I can provide an example to get you started. Also note that operator overloading for particular types will almost certainly work with CML1, and should work with CML2.

Hopefully this helps.
Cheers, Demian
dnave
 
Posts: 113
Joined: Fri Jun 29, 2007 12:46 am
Location: Pittsburgh, PA

Re: C++11 auto and promoted types

Postby sprite » Mon Dec 01, 2014 10:03 pm

Thanks for the reply.

Thinking about it some more, my style of using references is what's causing my problems as much as auto. I've been doing the following quite a bit in my code:

Code: Select all

foo.SetVelocity(cml::vector3f(1.f, 1.f, 1.f));

auto const& velocity = foo.GetVelocity(); // returns cml::vector3f const&

auto cpp11 = velocity * 5.f; // calculated later
cml::vector3f cpp98 = velocity * 5.f; // calculated now

foo.SetVelocity(cml::vector3f(0.f, 0.f, 0.f)); // ruh roh...

std::cout << cpp11 << std::endl; // prints out "0 0 0"
std::cout << cpp98 << std::endl; // prints out "5 5 5"



So to clarify. In CML2, the following will still print "0 0 0"?

I guess I'll just have to either explicitly mention types, or change my methods to return values instead of const references.
sprite
 
Posts: 6
Joined: Tue Jun 29, 2010 10:36 pm

Re: C++11 auto and promoted types

Postby dnave » Tue Dec 02, 2014 2:19 am

Your example is basically suffering from "aliasing", which can happen with references or pointers. You have two separate locations (foo and velocity) that refer to the same memory with different names. Generally, you'll want to write your code to avoid aliasing in cases where the result will be unexpected (as in your example) or even undefined (inadvertently referencing deleted memory, for example).

CML2 does help avoid inadvertent use of expired references, in particular when a compiler-generated temporary appears in the expression tree. However, it can't help when the user creates an aliasing situation outside of the expression tree. You can implement your suggestion of returning non-references from your functions, or you can just make sure to assign to the expected vector type. It's worth noting that "auto const&" and "cml::vector3f" differ by only 2 characters, so you haven't saved much by using "auto".

Moreover, overuse of "auto" can obscure your code. I know it's hard to avoid the temptation to use "auto" everywhere, but its primary purpose is to simplify code that would otherwise be totally opaque due to complex named types resulting from the use of templates. This is definitely the situation for CML2---for example, x in "auto x = a + 2*b;" has a pretty complex template class type specifying a 5-term expression tree. Just to be clear, you can't use "auto" here for a CML1 expression because there are two temporaries generated (one for "+" and one for "*") whose lifetimes expire at ";", resulting in references to destroyed memory later in the code. CML2 avoids this pitfall essentially by instantiating the temporaries in-place within the expression tree (via move constructors) so that the associated memory remains valid as long as x remains in scope.

Anyway, the short story is that, if you use references, you have to avoid modifying the referenced memory in unexpected or unintended ways. This will come with practice and experience, though there may even be tools available to help you.

Hope this helps!
Cheers, Demian
dnave
 
Posts: 113
Joined: Fri Jun 29, 2007 12:46 am
Location: Pittsburgh, PA


Return to Bug Reports

Who is online

Users browsing this forum: No registered users and 1 guest

cron