Search code examples
c++projection

Projection: Is it OK to take address of data member of STL container?


Let's take (as a demo example) a simple counting algorithm for getting the max count of characters in a string.

A typical C++17 implementation could be:

#include <iostream>
#include <unordered_map>
#include <string_view>
#include <algorithm>
#include <utility>

using Counter = std::unordered_map<char, std::size_t>;
using Pair = Counter::value_type;
constexpr std::string_view s{ "abbcccddddeeeeeffffff" };

int main() {
    
    Counter counter{};
    for (const char c : s) counter[c]++;

    const auto& [letter, count] = *std::max_element(counter.begin(), counter.end(), 
        [](Pair& p1, Pair& p2) { return p1.second < p2.second; });

    std::cout << "\n\nHighest count is '" << count << "' for letter '" << letter << "'\n\n";
}

In C++20 we have projections and can use pointer to structure member elements for the projection (and give that to the underlying std::invoke).

The solution would be a little bit shorter, not sure, if better (for whatever criteria). Anyway:

#include <iostream>
#include <unordered_map>
#include <string_view>
#include <algorithm>

using Counter = std::unordered_map<char, std::size_t>;
namespace rng = std::ranges;
constexpr std::string_view s{ "abbcccddddeeeeeffffff" };

int main() {

    Counter counter{};
    for (const char c : s) counter[c]++;

    const auto& [letter, count] = *rng::max_element(counter, {}, &Counter::value_type::second);

    std::cout << "\n\nHighest count is '" << count << "' for letter '" << letter << "'\n\n";
}

But, Im not sure about taking the address of a containers data member, residing in the std::namespace. Is this OK?


Solution

  • The only restrictions I see in [namespace.std] are about pointers to member functions. I can't find anything that would disallow taking a pointer to a (public) data member of a standard library class.

    This also makes sense, since the restrictions for functions are there to allow the standard library implementation to choose a different overload set than described in the standard, as long as direct calls still work as specified. However, there is no similar choice the implementation could make for a data member that is specified in the public interface (not for exposition only).

    So I don't see anything wrong with &Counter::value_type::second.