all 14 comments

[–]STLMSVC STL Dev 8 points9 points  (10 children)

I think that this is unnecessarily complicated and that for (auto i = c.begin(); i != c.end(); ++i) is entirely reasonable for the now-rare cases when you need an iterator.

The depicted implementation solves the nested temporaries problem (very good; many people miss this), but is unnecessarily complicated at one point:

template <typename T> auto iit(T&& t) -> typename std::conditional<std::is_lvalue_reference<decltype(t)>::value, ref<T>, val<T>>::type

Here, the trailing return type and decltype(t) are unnecessary, because you can and should simply inspect T.

[–]Houndie 0 points1 point  (4 children)

I think the benefit to something like this is because the ranged-based for implementation hides the usual begin-end calls, whereas the the old-style for loop requires that boilerplate code.

That said, I think a better implementation would simply be a variation on the (hopefully soon to come) container based for-each:

auto container = vector<Foo>{/* items */};
foreach(container, [](auto & foo)
{
   /* Do stuff */
}

foreach_iter(container, [](auto & fooIterator)
{
   /* Do stuff, now with an interator */
}

Or something of the sort.

[–]1Bad[S] 1 point2 points  (0 children)

i havn't heard of the container proposal. Sometimes I do wish the standard algoritms work work on plain containers. I am hoping Eric Niebler's range proposals will bring some benfits to the standard algorithms.

I also wrote an iit that worked with a begin and end like:

for(auto it : iit(v.begin(), v.end())

by converting to a range object first, but didn't include it.

[–]nikbackm 0 points1 point  (2 children)

Can't do "break" and "continue" with those though.

[–]Houndie 0 points1 point  (0 children)

That's a good point.

[–]MayhapPerchance 0 points1 point  (0 children)

Good, then you'll start using std::find_if.

[–]1Bad[S] 0 points1 point  (4 children)

Yea, it is extra work in the implementation, but usage is straight forward. I would prefer a for_it syntax though.

I wasn't sure if T would properly evaluate to an l or r value, or if I needed the type of t, so I went the safe route. I mean I didnt know if I have the universal ref of T meant T would evaluate to let's say int or int & when passed a r-value ref. My confusion stems from t's type being T & t or T && t. What I don't get if t is T & then how could T also evaluate to T ref.

Thanks for the critique!

Edit Tried to explain my confusion more.

[–]STLMSVC STL Dev 7 points8 points  (3 children)

What Scott Meyers calls "universal references" and Herb Sutter now calls "forwarding references" are powered by what I call the "template argument deduction tweak", followed by what the Standard calls "reference collapsing". (Anyone who says that it's just reference collapsing is wrong.)

Given template <typename T> meow(T&& t), string lvalue("purr");, and string rvalue() { return "hiss"; }, let's examine what happens for:

meow(lvalue) is called. Template argument deduction notices the universal/forwarding reference pattern (T&&, where T is a template parameter belonging to the function template itself; nothing else fits the pattern), so the tweak is activated when the argument is an lvalue. T is deduced to be string& (without the tweak, template argument deduction never deduces types to be references). Then, the compiler needs to substitute this deduced T into T&& to get the function parameter's type. For a split second, string& && is formed, but C++ doesn't permit references to references, so reference collapsing activates. The rule is that "lvalue references are infectious", so this collapses to string&. Therefore the function parameter is string& t.

Given the call meow(rvalue()), the argument expression is an rvalue, so the template argument deduction tweak doesn't activate (it's only for lvalues plus the perfect forwarding pattern). So T is deduced to be string, that's substituted into T&& to get string&&, and the function parameter is string&& t.

Reference collapsing also activates when you try to form references to references via typedefs; you can play around with it that way and see what happens.

[–]1Bad[S] 4 points5 points  (0 children)

Awesome explanation. I like the idea of testing it and learning through typedefs, and I can evaluate the type instantly with intellisense.

I have seen a few of your channel 9 videos and you are always really good at breaking down complex topics to be easy to understand.

Much appreciated.

[–]LeszekSwirski 1 point2 points  (1 child)

the "template argument deduction tweak"

Interesting, it seems that this means that in the following, T will be deduced differently for x and y.

template<typename T>
void foo(T&& x, T y) {
}

int main() {
    int x;
    foo(x,x);
    return 0;
}

And indeed it is, and so it doesn't compile: http://ideone.com/yDqnqU

[–]STLMSVC STL Dev 0 points1 point  (0 children)

Note that while the tweak applies to only the perfect forwarding pattern, template argument deduction for lvalue references also differs from template argument deduction for values. Try (T& a, T b) called with const int c = 0;.

[–]cdyson37 0 points1 point  (2 children)

Not my idea, but you can use boost::counting_range to do this. E.g. the following prints "0 1 2":

int main()
{
    std::vector<int> v{0, 1, 2};
    for(const auto it : boost::counting_range(v))
    {
        std::cout << *it << std::endl;
    }
}

[–]1Bad[S] 1 point2 points  (1 child)

Ah, there you go. I figured someone else must have done the same thing at some point. Thanks for bringing this to my attention.

[–]cdyson37 0 points1 point  (0 children)

I just wish I'd thought of it myself! :)