Range-based for loop on unordered_map and references

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP











up vote
9
down vote

favorite
5












When running a range-based for loop on an std::unordered_map it appears that the type of the loop variable does not use reference types:



std::unordered_map<int, int> map = 0, 1, 1, 2, 2, 3 ;
for(auto&[l, r] : map)
static_assert(std::is_same_v<decltype(r), int&>);


MSVC 2017, gcc 8.2 and clang 7.0.0 all report a failed assertion here. Oppose this to a std::vector, where the assertion does not fail, as one would expect:



std::vector<int> vec = 1, 2, 3 ;
for(auto& r : vec)
static_assert(std::is_same_v<decltype(r), int&>);


However on both MSVC 2017 and gcc 8.2 a loop modifying the local variable r will have observable side-effects:



#include <iostream>
#include <type_traits>
#include <unordered_map>
#include <vector>

int main()
std::unordered_map<int, int> a = 0, 1, 1, 2, 2, 3 ;
for(auto[l, r] : a)
std::cout << l << "; " << r << std::endl;
for(auto&[l, r] : a)
static_assert(std::is_same_v<decltype(r), int>);
r++;

std::cout << "Increment:" << std::endl;
for(auto[l, r] : a)
std::cout << l << "; " << r << std::endl;



This program for example will print (ignoring order):



0; 1
1; 2
2; 3
Increment:
0; 2
1; 3
2; 4


What am I missing?
How can this change the value in the map despite the local variable not being of reference type?
Or probably more appropriately, why does std::is_same not see the right type, because quite clearly it is a reference type?
Or am I alternatively missing some undefined behavior?



Note that I did reproduce the same issue without using structured bindings, so I keep the nice looking code here.
See here for an example










share|improve this question



















  • 3




    The culprit is decltype: "If the argument is an unparenthesized id-expression naming a structured binding, then decltype yields the referenced type". But I admit I have no clue what's the rationale for that.
    – HolyBlackCat
    44 mins ago











  • A smaller failing example: std::pair<const int, int> foo1,2; auto & [first, second] = foo; static_assert(std::is_same_v<decltype(second), int&>);
    – Michael Veksler
    36 mins ago






  • 1




    @HolyBlackCat to make C++ even more confusing and less consistent. Seems to be the rationale for everything we added in the last years.
    – inf
    34 mins ago











  • What are the types of identifiers introduced by structured bindings in C++17?; structured bindings: when something looks like a reference and behaves similarly to a reference, but it's not a reference
    – cpplearner
    20 mins ago














up vote
9
down vote

favorite
5












When running a range-based for loop on an std::unordered_map it appears that the type of the loop variable does not use reference types:



std::unordered_map<int, int> map = 0, 1, 1, 2, 2, 3 ;
for(auto&[l, r] : map)
static_assert(std::is_same_v<decltype(r), int&>);


MSVC 2017, gcc 8.2 and clang 7.0.0 all report a failed assertion here. Oppose this to a std::vector, where the assertion does not fail, as one would expect:



std::vector<int> vec = 1, 2, 3 ;
for(auto& r : vec)
static_assert(std::is_same_v<decltype(r), int&>);


However on both MSVC 2017 and gcc 8.2 a loop modifying the local variable r will have observable side-effects:



#include <iostream>
#include <type_traits>
#include <unordered_map>
#include <vector>

int main()
std::unordered_map<int, int> a = 0, 1, 1, 2, 2, 3 ;
for(auto[l, r] : a)
std::cout << l << "; " << r << std::endl;
for(auto&[l, r] : a)
static_assert(std::is_same_v<decltype(r), int>);
r++;

std::cout << "Increment:" << std::endl;
for(auto[l, r] : a)
std::cout << l << "; " << r << std::endl;



This program for example will print (ignoring order):



0; 1
1; 2
2; 3
Increment:
0; 2
1; 3
2; 4


What am I missing?
How can this change the value in the map despite the local variable not being of reference type?
Or probably more appropriately, why does std::is_same not see the right type, because quite clearly it is a reference type?
Or am I alternatively missing some undefined behavior?



Note that I did reproduce the same issue without using structured bindings, so I keep the nice looking code here.
See here for an example










share|improve this question



















  • 3




    The culprit is decltype: "If the argument is an unparenthesized id-expression naming a structured binding, then decltype yields the referenced type". But I admit I have no clue what's the rationale for that.
    – HolyBlackCat
    44 mins ago











  • A smaller failing example: std::pair<const int, int> foo1,2; auto & [first, second] = foo; static_assert(std::is_same_v<decltype(second), int&>);
    – Michael Veksler
    36 mins ago






  • 1




    @HolyBlackCat to make C++ even more confusing and less consistent. Seems to be the rationale for everything we added in the last years.
    – inf
    34 mins ago











  • What are the types of identifiers introduced by structured bindings in C++17?; structured bindings: when something looks like a reference and behaves similarly to a reference, but it's not a reference
    – cpplearner
    20 mins ago












up vote
9
down vote

favorite
5









up vote
9
down vote

favorite
5






5





When running a range-based for loop on an std::unordered_map it appears that the type of the loop variable does not use reference types:



std::unordered_map<int, int> map = 0, 1, 1, 2, 2, 3 ;
for(auto&[l, r] : map)
static_assert(std::is_same_v<decltype(r), int&>);


MSVC 2017, gcc 8.2 and clang 7.0.0 all report a failed assertion here. Oppose this to a std::vector, where the assertion does not fail, as one would expect:



std::vector<int> vec = 1, 2, 3 ;
for(auto& r : vec)
static_assert(std::is_same_v<decltype(r), int&>);


However on both MSVC 2017 and gcc 8.2 a loop modifying the local variable r will have observable side-effects:



#include <iostream>
#include <type_traits>
#include <unordered_map>
#include <vector>

int main()
std::unordered_map<int, int> a = 0, 1, 1, 2, 2, 3 ;
for(auto[l, r] : a)
std::cout << l << "; " << r << std::endl;
for(auto&[l, r] : a)
static_assert(std::is_same_v<decltype(r), int>);
r++;

std::cout << "Increment:" << std::endl;
for(auto[l, r] : a)
std::cout << l << "; " << r << std::endl;



This program for example will print (ignoring order):



0; 1
1; 2
2; 3
Increment:
0; 2
1; 3
2; 4


What am I missing?
How can this change the value in the map despite the local variable not being of reference type?
Or probably more appropriately, why does std::is_same not see the right type, because quite clearly it is a reference type?
Or am I alternatively missing some undefined behavior?



Note that I did reproduce the same issue without using structured bindings, so I keep the nice looking code here.
See here for an example










share|improve this question















When running a range-based for loop on an std::unordered_map it appears that the type of the loop variable does not use reference types:



std::unordered_map<int, int> map = 0, 1, 1, 2, 2, 3 ;
for(auto&[l, r] : map)
static_assert(std::is_same_v<decltype(r), int&>);


MSVC 2017, gcc 8.2 and clang 7.0.0 all report a failed assertion here. Oppose this to a std::vector, where the assertion does not fail, as one would expect:



std::vector<int> vec = 1, 2, 3 ;
for(auto& r : vec)
static_assert(std::is_same_v<decltype(r), int&>);


However on both MSVC 2017 and gcc 8.2 a loop modifying the local variable r will have observable side-effects:



#include <iostream>
#include <type_traits>
#include <unordered_map>
#include <vector>

int main()
std::unordered_map<int, int> a = 0, 1, 1, 2, 2, 3 ;
for(auto[l, r] : a)
std::cout << l << "; " << r << std::endl;
for(auto&[l, r] : a)
static_assert(std::is_same_v<decltype(r), int>);
r++;

std::cout << "Increment:" << std::endl;
for(auto[l, r] : a)
std::cout << l << "; " << r << std::endl;



This program for example will print (ignoring order):



0; 1
1; 2
2; 3
Increment:
0; 2
1; 3
2; 4


What am I missing?
How can this change the value in the map despite the local variable not being of reference type?
Or probably more appropriately, why does std::is_same not see the right type, because quite clearly it is a reference type?
Or am I alternatively missing some undefined behavior?



Note that I did reproduce the same issue without using structured bindings, so I keep the nice looking code here.
See here for an example







c++ language-lawyer c++17 unordered-map structured-bindings






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited 33 mins ago









Swordfish

5,7851932




5,7851932










asked 53 mins ago









Hanno Bänsch

9826




9826







  • 3




    The culprit is decltype: "If the argument is an unparenthesized id-expression naming a structured binding, then decltype yields the referenced type". But I admit I have no clue what's the rationale for that.
    – HolyBlackCat
    44 mins ago











  • A smaller failing example: std::pair<const int, int> foo1,2; auto & [first, second] = foo; static_assert(std::is_same_v<decltype(second), int&>);
    – Michael Veksler
    36 mins ago






  • 1




    @HolyBlackCat to make C++ even more confusing and less consistent. Seems to be the rationale for everything we added in the last years.
    – inf
    34 mins ago











  • What are the types of identifiers introduced by structured bindings in C++17?; structured bindings: when something looks like a reference and behaves similarly to a reference, but it's not a reference
    – cpplearner
    20 mins ago












  • 3




    The culprit is decltype: "If the argument is an unparenthesized id-expression naming a structured binding, then decltype yields the referenced type". But I admit I have no clue what's the rationale for that.
    – HolyBlackCat
    44 mins ago











  • A smaller failing example: std::pair<const int, int> foo1,2; auto & [first, second] = foo; static_assert(std::is_same_v<decltype(second), int&>);
    – Michael Veksler
    36 mins ago






  • 1




    @HolyBlackCat to make C++ even more confusing and less consistent. Seems to be the rationale for everything we added in the last years.
    – inf
    34 mins ago











  • What are the types of identifiers introduced by structured bindings in C++17?; structured bindings: when something looks like a reference and behaves similarly to a reference, but it's not a reference
    – cpplearner
    20 mins ago







3




3




The culprit is decltype: "If the argument is an unparenthesized id-expression naming a structured binding, then decltype yields the referenced type". But I admit I have no clue what's the rationale for that.
– HolyBlackCat
44 mins ago





The culprit is decltype: "If the argument is an unparenthesized id-expression naming a structured binding, then decltype yields the referenced type". But I admit I have no clue what's the rationale for that.
– HolyBlackCat
44 mins ago













A smaller failing example: std::pair<const int, int> foo1,2; auto & [first, second] = foo; static_assert(std::is_same_v<decltype(second), int&>);
– Michael Veksler
36 mins ago




A smaller failing example: std::pair<const int, int> foo1,2; auto & [first, second] = foo; static_assert(std::is_same_v<decltype(second), int&>);
– Michael Veksler
36 mins ago




1




1




@HolyBlackCat to make C++ even more confusing and less consistent. Seems to be the rationale for everything we added in the last years.
– inf
34 mins ago





@HolyBlackCat to make C++ even more confusing and less consistent. Seems to be the rationale for everything we added in the last years.
– inf
34 mins ago













What are the types of identifiers introduced by structured bindings in C++17?; structured bindings: when something looks like a reference and behaves similarly to a reference, but it's not a reference
– cpplearner
20 mins ago




What are the types of identifiers introduced by structured bindings in C++17?; structured bindings: when something looks like a reference and behaves similarly to a reference, but it's not a reference
– cpplearner
20 mins ago












1 Answer
1






active

oldest

votes

















up vote
6
down vote













Structured bindings are modeled as aliases, not "real" references. Even though they may use a reference under the hood.



Imagine that you have



struct X 
const int first = 0;
int second;
int third : 8;
;

X x;
X& y = x;


What's decltype(x.second)? int. What's decltype(y.second)? int. And so in



auto& [first, second, third] = x;


decltype(second) is int, because second is an alias for x.second. And third poses no problems even though it's not allowed to bind a reference to a bit-field, because it's an alias, not an actual reference.



The tuple-like case is designed to be consistent with that. Even though in that case the language has to use references, it does its best to pretend that those references do not exist.






share|improve this answer




















    Your Answer





    StackExchange.ifUsing("editor", function ()
    StackExchange.using("externalEditor", function ()
    StackExchange.using("snippets", function ()
    StackExchange.snippets.init();
    );
    );
    , "code-snippets");

    StackExchange.ready(function()
    var channelOptions =
    tags: "".split(" "),
    id: "1"
    ;
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function()
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled)
    StackExchange.using("snippets", function()
    createEditor();
    );

    else
    createEditor();

    );

    function createEditor()
    StackExchange.prepareEditor(
    heartbeatType: 'answer',
    convertImagesToLinks: true,
    noModals: false,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    bindNavPrevention: true,
    postfix: "",
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    );



    );













     

    draft saved


    draft discarded


















    StackExchange.ready(
    function ()
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f52914597%2frange-based-for-loop-on-unordered-map-and-references%23new-answer', 'question_page');

    );

    Post as a guest






























    1 Answer
    1






    active

    oldest

    votes








    1 Answer
    1






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes








    up vote
    6
    down vote













    Structured bindings are modeled as aliases, not "real" references. Even though they may use a reference under the hood.



    Imagine that you have



    struct X 
    const int first = 0;
    int second;
    int third : 8;
    ;

    X x;
    X& y = x;


    What's decltype(x.second)? int. What's decltype(y.second)? int. And so in



    auto& [first, second, third] = x;


    decltype(second) is int, because second is an alias for x.second. And third poses no problems even though it's not allowed to bind a reference to a bit-field, because it's an alias, not an actual reference.



    The tuple-like case is designed to be consistent with that. Even though in that case the language has to use references, it does its best to pretend that those references do not exist.






    share|improve this answer
























      up vote
      6
      down vote













      Structured bindings are modeled as aliases, not "real" references. Even though they may use a reference under the hood.



      Imagine that you have



      struct X 
      const int first = 0;
      int second;
      int third : 8;
      ;

      X x;
      X& y = x;


      What's decltype(x.second)? int. What's decltype(y.second)? int. And so in



      auto& [first, second, third] = x;


      decltype(second) is int, because second is an alias for x.second. And third poses no problems even though it's not allowed to bind a reference to a bit-field, because it's an alias, not an actual reference.



      The tuple-like case is designed to be consistent with that. Even though in that case the language has to use references, it does its best to pretend that those references do not exist.






      share|improve this answer






















        up vote
        6
        down vote










        up vote
        6
        down vote









        Structured bindings are modeled as aliases, not "real" references. Even though they may use a reference under the hood.



        Imagine that you have



        struct X 
        const int first = 0;
        int second;
        int third : 8;
        ;

        X x;
        X& y = x;


        What's decltype(x.second)? int. What's decltype(y.second)? int. And so in



        auto& [first, second, third] = x;


        decltype(second) is int, because second is an alias for x.second. And third poses no problems even though it's not allowed to bind a reference to a bit-field, because it's an alias, not an actual reference.



        The tuple-like case is designed to be consistent with that. Even though in that case the language has to use references, it does its best to pretend that those references do not exist.






        share|improve this answer












        Structured bindings are modeled as aliases, not "real" references. Even though they may use a reference under the hood.



        Imagine that you have



        struct X 
        const int first = 0;
        int second;
        int third : 8;
        ;

        X x;
        X& y = x;


        What's decltype(x.second)? int. What's decltype(y.second)? int. And so in



        auto& [first, second, third] = x;


        decltype(second) is int, because second is an alias for x.second. And third poses no problems even though it's not allowed to bind a reference to a bit-field, because it's an alias, not an actual reference.



        The tuple-like case is designed to be consistent with that. Even though in that case the language has to use references, it does its best to pretend that those references do not exist.







        share|improve this answer












        share|improve this answer



        share|improve this answer










        answered 22 mins ago









        T.C.

        102k13207311




        102k13207311



























             

            draft saved


            draft discarded















































             


            draft saved


            draft discarded














            StackExchange.ready(
            function ()
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f52914597%2frange-based-for-loop-on-unordered-map-and-references%23new-answer', 'question_page');

            );

            Post as a guest













































































            Comments

            Popular posts from this blog

            What does second last employer means? [closed]

            List of Gilmore Girls characters

            Confectionery