Search code examples
c++referenceiteratortuplesc++23

Why does a C++ iterator reference need to be const?


I'm trying out this demo code to play with std::ranges::views::cartesian_product in g++13. I'm wondering about the for loop in main:

#include <cstdio>
#include <tuple>
#include <array>
#include <iostream>
#include <ranges>

typedef  std::tuple<int const&, int const&, int const&> int_triple;

void print(int_triple x){
    std::cout << std::get<0>(x) << " " <<std::get<1>(x) << " "<<std::get<2>(x) << " " << std::endl;
}

int main(void){
    std::array<int, 2> x = {1,2};
    std::array<int, 2> y = {3,4};
    std::array<int, 2> z = {5, 6};

    for(const int_triple& t: std::views::cartesian_product(x,y,z)){
        print(t);
    }

    return 0;
}

Why is this OK:

for(const int_triple& t: std::views::cartesian_product(x,y,z))

but this throws the following error:

for(int_triple& t: std::views::cartesian_product(x,y,z))
cartprod_demo.cpp:16:59: error: cannot bind non-const lvalue reference of type ‘int_triple&’ {aka ‘std::tuple<const int&, const int&, const int&>&’} to an rvalue of type ‘int_triple’ {aka ‘std::tuple<const int&, const int&, const int&>’}
   16 |     for(int_triple& t: std::views::cartesian_product(x,y,z)){
      |                                                           ^
In file included from cartprod_demo.cpp:2:
/usr/include/c++/13/tuple:930:9: note:   after user-defined conversion: ‘constexpr std::tuple< <template-parameter-1-1> >::tuple(std::tuple<_Args1 ...>&&) [with _UElements = {int&, int&, int&}; bool _Valid = true; typename std::enable_if<_TCC<_Valid>::__is_implicitly_constructible<_UElements ...>(), bool>::type <anonymous> = true; _Elements = {const int&, const int&, const int&}]’

How do I read this error?

I'm new to C++, so I want to understand this terse syntax. I get that the lvalue is just the variable t on the left-hand side, but that's pretty much all I can get from this.


Solution

  • cartesian_product() returns a view, and then the for loop uses that view's iterators to access the view's elements. The for loop dereferences those iterators to access the individual elements of the view and assign them to your t variable.

    Your code:

    for(const int_triple& t: std::views::cartesian_product(x,y,z)){
        print(t);
    }
    

    Is effectively equivalent to this:

    {
        auto&& range = std::views::cartesian_product(x,y,z);
        auto begin = range.begin();
        auto end = range.end();
        for ( ; begin != end; ++begin)
        {
            const int_triple& t = *begin; // <-- problem here
            print(t);
        }
    }
    

    In this situation, the iterator dereference is returning each tuple element by value, thus they are rvalues.

    A const reference can bind to an rvalue, which is why const int_triple& t works, but a non-const reference cannot, which is why int_triple& t fails. And that is exactly what the error message is complaining about:

    cannot bind non-const lvalue reference of type ‘int_triple&’ ... to an rvalue of type ‘int_triple’ ...