Reputation: 91
Below is an example that I think illustrates a case where it is preferable to use const_iterator to "const auto". This is because containers don't provide a cfind() function. Is there any alternative? Or should "const auto" be used and the lack of const be ignored?
std::string GetJobTitle(const std::string& employee)
{
using EmployeeTitles = std::unordered_map<std::string, std::string>;
EmployeeTitles employees = { { "Alice", "Director" },{ "Bob","Manager" } ,{ "Charlie", "Developer" } };
// Option 1. const_iterator is access only:
EmployeeTitles::const_iterator itr = employees.(employee);
if (itr != employees.cend())
{
itr->second = "Project Manager"; // error C2678: The compiler prevents us changing the job tile which is what we want
return itr->second;
}
// Option 2. const auto is more concise but is not as safe:
const auto& itr2 = employees.find(employee);
if (itr2 != employees.cend())
{
itr2->second = "Project Manager"; // employee now has new title - how can we prevent this with the compiler but still use auto?
return itr2->second;
}
return "";
}
Upvotes: 6
Views: 1236
Reputation: 2647
Use const
variables
Your example doesn’t illustrate a good case for the problem. Just be more “aggressive” with constness and it disappears. You do not change employees
at all, so the proper solution is to declare it const
in the first place:
const EmployeeTitles employees = ...;
That’s even safer because it prevents changes to employees
anywhere, not just changes through the iterator.
Use scoping to separate const/non-const code
What if you cannot make employees
const because you can only populate it piece by piece; for example because you pull the information from a database? Move the populating code into a builder function. Or for simple cases use an immediately invoked lambda:
const EmployeeTitles employees = [] {
EmployeeTitles employees;
for (const auto& record : database.employees()) {
// Probably some more processing would be done here in the real world.
employees.emplace(record.name(), record.job_title());
}
return employees;
}();
Use const
member functions
If employees
is a member variable of a class and you iterate over it in a member function, make that function const
.
As a general rule
Whenever you run into this problem or something similar, think of ways to use const
variables/functions and/or scoping to sidestep it altogether. That will take care of the majority of cases.
In that case I’d go with your option 1: Explicitely declaring the iterator const_iterator
in combination with the using
declaration for the map type. It’s concise, readable, immediately understandable, and the most direct way to express your intent.
Other solutions that manipulate the constness of employees
aren’t as good because that’s not what you really care about. What you actually want is a read-only iterator. Fiddling with the constness of employees
is just a roundabout way to reach that goal. And roundabout code is harder to understand.
On the other hand that doesn’t mean you’ll run into huge problems with clarity. Especially std::as_const
is nice and concise as well.
However with a pre-C++17 code base you’d have to use a const_cast
. It’s a benign one because it adds const and it’s not too verbose, either. But I’d avoid it on the general principle that seeing a const_cast
in a piece of code is always a bit scary on first glance. Another good possibility, as @Swift pointed out in the comments, is to implement your own version of as_const
.
Upvotes: 5