Case Study: Range Based for
On std::map
¶
Gool Ol’ Days: Iteration In C++ < 11¶
Loud!
Cannot simply initialize
std::map
with contentInitialize empty map
Fill it in regular code flow
Iterators are cool only if one likes pointer arithmetic (otherwise not)
Manually unpacking a
std::pair
is not so cool
#include <gtest/gtest.h>
#include <map>
#include <string>
static void do_something_with(int, const std::string&) { /*...*/ }
TEST(range_based_for_suite, map_pre_11)
{
using table = std::map<int, std::string>;
// no initialization in C++<11; have to insert at runtime
table translations;
translations.insert(std::make_pair(0, "zero"));
translations.insert(std::make_pair(1, "one"));
translations.insert(std::make_pair(2, "two"));
// iteration using iterator objects (c/v pointer arithmetic)
for (table::const_iterator it=translations.begin(); it!=translations.end(); ++it) {
int num = it->first;
std::string str = it->second;
do_something_with(num, str);
}
}
Basic C++11 Iteration: Range Based For, auto
, And Initialization¶
New initialization syntax (user’s view, implementor’s view)
Compiler knows the type of a
std::map
iterator anyway⟶ let him write the type
⟶ auto
#include <gtest/gtest.h>
#include <map>
#include <string>
static void do_something_with(int, const std::string&) { /*...*/ }
TEST(range_based_for_suite, map_basic)
{
using table = std::map<int, std::string>;
table translations {
{0, "zero"},
{1, "one"},
{2, "two"},
};
for (auto elem: translations) {
auto num = elem.first;
auto str = elem.second;
do_something_with(num, str);
}
}
Variation: Preventing Accidental Copy Using const auto&
¶
Using plain auto
can be expensive …
for (auto elem: translations) {
...
}
std::pair<int, std::string>
is usually cheap (depends onstd::string
implementation though)But what if iterating over
std::map<int, std::vector<std::string>>
?⟶ Copying a possibly huge
std::vector
!
#include <gtest/gtest.h>
#include <map>
#include <string>
static void do_something_with(int, const std::string&) { /*...*/ }
TEST(range_based_for_suite, map_basic_const_auto_reference)
{
using table = std::map<int, std::string>;
table translations {
{0, "zero"},
{1, "one"},
{2, "two"},
};
for (const auto& elem: translations) {
auto num = elem.first;
const auto& str = elem.second;
do_something_with(num, str);
}
}
Still unwanted though: manual std::pair
unpacking
Pythonicity: Tuple Unpacking, err Structured Binding¶
Wanted: Python’s tuple unpacking, as used in dictionary iteration
translations = { 0: 'zero', 1: 'one', 2: 'two' }
for key, value in translations.items():
do_something_with(key, value)
C++-ification of Python
Comes in many flavors since C++ cares more about types, obviously
Structured binding: simplest usage - by copy
#include <gtest/gtest.h>
#include <map>
#include <string>
static void do_something_with(int, const std::string&) { /*...*/ }
TEST(range_based_for_suite, map_unpack_copy)
{
using table = std::map<int, std::string>;
table translations {
{0, "zero"},
{1, "one"},
{2, "two"},
};
for (auto [num, str]: translations)
do_something_with(num, str);
}
Structured Binding - Preventing Accidental Copy¶
auto understands references and
const
referencesstructured binding relies on auto
⟶ let’s use references more …
#include <gtest/gtest.h>
#include <map>
#include <string>
static void do_something_with(int, const std::string&) { /*...*/ }
TEST(range_based_for_suite, map_unpack_reference)
{
using table = std::map<int, std::string>;
table translations {
{0, "zero"},
{1, "one"},
{2, "two"},
};
for (auto& [num, str]: translations)
do_something_with(num, str);
}
#include <gtest/gtest.h>
#include <map>
#include <string>
static void do_something_with(int, const std::string&) { /*...*/ }
TEST(range_based_for_suite, map_unpack_const_reference)
{
using table = std::map<int, std::string>;
table translations {
{0, "zero"},
{1, "one"},
{2, "two"},
};
for (const auto& [num, str]: translations)
do_something_with(num, str);
}