Expression templates and ranged based for in C++11
Asked Answered
R

2

9

It is my understanding that expression templates will break on ranged based for in C++11, as for (auto x : expr) has an implicit auto&& __range = expr in it, and this will result in dangling references.

Is there a way create expression template classes so that they either behave correctly with ranged based for, or at the very least throw a compile error?

Basically, I'd like to prevent the possibility that expression templates would correctly compile but fail at run time due to dangling references. I don't mind having to wrap expression templates in something before using them in a ranged based for, as long as there are no silent run-time errors if the user forgets to wrap the expression templates.

Reconvert answered 1/3, 2012 at 1:51 Comment(5)
The auto&& in the range-based for loop might really turn out to be something to shoot oneself in the foot easily – I still haven't really understood what range types exactly are affected, and why (lvalue references to non-const: dangerous, non-references: unproblematic, ???).Vincentia
@Philipp: There is no such thing as a "range type". There are simply types that conform to the range "concept". Specifically, that there are a pair of begin/end overrides that return input iterators.Kalif
I guess the answer is to make sure expression templates do not conform to the range "concept", i.e. they do not have begin and end.Reconvert
@NicolBolas: Sorry for the sloppy language, I was thinking about the expression category taxonomy, not about range concepts. Probably the answer is just "references to temporaries might become dangling". @Clinton: That helps in this case, but not for examples like string("a") += string("b").Vincentia
Hi, It is not clear what your goal is. Do you want to provide an expression template-based library and be able to use it in foreach loop? Or do you have a library and <em>do not</em> want it to compile with foreach? Or do you want it to compile and have no dangling reference problem?Petrolic
S
2

There's a few options I can think of, each with their own ugliness.

One obvious option is to use pointers (probably unique_ptr) instead of references. Of course, in order for this to work, it either requires allocation from the heap, or custom allocators. I think with a good allocator, this approach has some merits. Then again, the operator overloading would just get nasty.

Another approach is to store sub-expressions by value instead of by const reference. The efficiency of this approach is very compiler-dependant, but since you're basically dealing with a bunch of temporaries, I would imagine that modern compilers can optimize away the copies (or at least, a lot of the copies).

The last approach allows you to keep the same structure to your code, but forces the user to evaluate the expression. It requires that you have only one iterable type, which is the underlying type of the expression (say, std::vector<int>). None of the expression classes should have begin and end methods or functions defined for them, but should just be convertible to the underlying type. This way, code like for(auto x : expr) will fail at compile-time (since expr is not iterable), but writing for(auto x : static_cast<vector<int>>(expr)) works because the expression is already evaluated.

If you were hoping to use range-based for loops to implement expression template operations, then you can provide private or protected begin and end methods in your expression template classes. Just make sure each template class can access the begin and end methods of the other template classes. It should be okay in this context since the expression template is a parameter to the function, so you won't have to worry about dangling references when writing the loop within that function.

Stutman answered 7/3, 2012 at 7:42 Comment(0)
K
6

There's generally nothing you can do about this. If you give an expression as the range, it must resolve to something that will be valid after the initialization of the for statement. And there's no way to detect at compile-time that any particular type was deduced by auto.

It would be better to make your expression system more move-based, so that it doesn't have to hold references. That will yield much safer results with auto than trying to store references to potentially dead things. If the copying for non-movable types troubles you, then just live with it.

Kalif answered 1/3, 2012 at 1:54 Comment(2)
That doesn't solve the problem. Your range-based for loop contains an implicit auto &&__range = listOfInt;, which could cause problems if listOfInt was actually a value of some expression template class.Gynandry
@RichardSmith: That's true, I think in that case the only thing one could do is introducing an explicit cast or a new automatic variable.Vincentia
S
2

There's a few options I can think of, each with their own ugliness.

One obvious option is to use pointers (probably unique_ptr) instead of references. Of course, in order for this to work, it either requires allocation from the heap, or custom allocators. I think with a good allocator, this approach has some merits. Then again, the operator overloading would just get nasty.

Another approach is to store sub-expressions by value instead of by const reference. The efficiency of this approach is very compiler-dependant, but since you're basically dealing with a bunch of temporaries, I would imagine that modern compilers can optimize away the copies (or at least, a lot of the copies).

The last approach allows you to keep the same structure to your code, but forces the user to evaluate the expression. It requires that you have only one iterable type, which is the underlying type of the expression (say, std::vector<int>). None of the expression classes should have begin and end methods or functions defined for them, but should just be convertible to the underlying type. This way, code like for(auto x : expr) will fail at compile-time (since expr is not iterable), but writing for(auto x : static_cast<vector<int>>(expr)) works because the expression is already evaluated.

If you were hoping to use range-based for loops to implement expression template operations, then you can provide private or protected begin and end methods in your expression template classes. Just make sure each template class can access the begin and end methods of the other template classes. It should be okay in this context since the expression template is a parameter to the function, so you won't have to worry about dangling references when writing the loop within that function.

Stutman answered 7/3, 2012 at 7:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.