Document number: D0890R0
Project: Programming Language C++
Audience: Evolution Working group
 
Antony Polukhin <antoshkka@gmail.com>, <antoshkka@yandex-team.ru>
 
Date: 2018-01-08

Safe range based for

I. Quick Introduction

[stmt.ranged] in the current working draft provides definition of the range based for that is easy to misuse:

#include <iostream>
#include <vector>

struct test {
    std::vector<int> val = {1, 2, 3};

    std::vector<int>& get() { return val; }

    ~test() {
        std::cerr << "~test() \n";
    }
};


int main() {
    for (auto&& v: test().get()) {
        std::cerr << v << ' ';
    }
}

In the above example call to the destructor for temporary test variable happens before the iteration over the returned value. This leads to the following output:

~test() 
0 0 3

This paper proposes a solution for the above problem and for two core issues CWG 900 and CWG 1498.

II. Wording

Modify the [stmt.ranged] paragraph 1 to keep the for-range-initializer expression for the whole execution time of the range:

The range-based for statement:

for (init-statementopt for-range-declaration : for-range-initializer ) statement

is equivalent to

    {
        init-statementopt
        [&](auto &&__range) {
            auto &&__range = for-range-initializer ;
            auto __begin = begin-expr ;
            auto __end = end-expr ;
            for ( ; __begin != __end; ++__begin ) {
                for-range-declaration = *__begin;
                statement
            }
        }(for-range-initializer);
    }
    

This change makes sure that for-range-initializer lives long enough to finish the iteration:

#include <iostream>
#include <vector>

struct test {
    std::vector<int> val = {1, 2, 3};

    std::vector<int>& get() { return val; }

    ~test() {
        std::cerr << "~test() \n";
    }
};


int main() {
    [&](auto&& __range) {
        auto __begin = std::begin(__range) ;
        auto __end = std::end(__range) ;
        for ( ; __begin != __end; ++__begin ) {
            auto&& v = *__begin;
            std::cerr << v << ' ';
        }
    }(test().get());
}

Code from above outputs:

1 2 3 ~test() 

III. Feature-testing macro

For the purposes of SG10, we recommend the feature-testing macro name __cpp_safe_range_based_for.