create iterator to map value?

Hi

I am designing a class which will have a private member which is of type map<ID, Something>. I would like the user of the class to be able to iterate over every Something in the class but without ever being able to "see" the ID. The ID is important for other reasons.

Is there a way of doing this? I show some code below which should indicate what I am trying to achieve but I'm not sure how to implement begin() and end()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <iostream>
#include <map>

using namespace std;

class Something
{
    
    public:
    Something (std::string s)
    {
        str = s;
    }
    
    Something()
    {
    }
    
    string getStr()
    {
        return str;   
    }
    
    private:
    std::string str;
    
};

class Holder
{
  
  private:
    using ID = uint8_t;    
    std::map<ID, Something> obs;
  
  public:
  
    void addSomething(Something sthing)
    {
        ID randomID = generateFreeId(); // generate an ID   
        obs.emplace(id, sthing);
    }
    
    Someiterator begin()
    {
        // how can I return an iterator to obs[first]?
    }
    
    Someiterator end()
    {
        // how can I return an iterator to obs[last]?

    }
    
    
};


int main()
{
    Holder holder;
    holder.addSomething(Something something("something 1"));
    holder.addSomething(Something something("something 2"));
    
    for (auto it = holder.begin(), it != holder.end(); ++it)
    {
        std::cout << it->getStr();
    }

}


How can I iterate over
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
#include <map>
#include <string>

struct something { std::string str ; } ;

struct holder
{
    using id_type = unsigned long long ;
    using map_type = std::map<id_type,something> ;
    using iterator = map_type::const_iterator ;

    iterator begin() const noexcept { return objects.begin() ; }
    iterator end() const noexcept { return objects.end() ; }

    holder& add( something s ) { objects.emplace( ++next_id, std::move(s) ) ; return *this ; }

    private:
        map_type objects ;
        static id_type next_id ;
};

unsigned long long holder::next_id = 0 ;

int main()
{
    holder my_objects ;
    my_objects.add( {"abcd"} ).add( {"efgh"} ).add( {"ijkl"} ).add( {"mnop"} ).add( {"qrst"} ) ;
    for( const auto& [id,thing] : my_objects )
        std::cout << id << " -- {" << thing.str << "}\n" ;
}

http://coliru.stacked-crooked.com/a/de2854793fc0210d
one way to do this would be slightly off-putting to some people (some folks have a hateful grudge against the technique):
- inherit from map into your own object. you would provide your own iterators and maybe the first() function so that they can't access the key from any path.

That stops the nice people, but its highly likely that your keys exist in memory and are reachable via pointer tricks from a less nice person. You can't let them call any base class stuff that would give access to the key!

So a better answer would be to encrypt the keys and let them alone, the user can see them but not do anything with them as-provided.

another way is 2 maps, one with the key and a fake key, and the other with data and fake key, aka parallel arrays style. The fake key is exposed, the real one is hidden in your private second map. Again, maybe possible to hack memory though. This is sort of standard on databases -- you don't want your keys to be data, but arbitrary 'account numbers' that are not associated with the entity, for privacy and security reasons.
Last edited on
> I would like the user of the class to be able to iterate over every Something in the class
> but without ever being able to "see" the ID. The ID is important for other reasons.

Provide a custom iterator that iterates (only) over the mapped_type.

For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <iostream>
#include <map>
#include <string>

struct something { std::string str ; } ;

struct holder
{
    using id_type = unsigned long long ;
    using map_type = std::map<id_type,something> ;
    struct iterator
    {
        using iterator_category = std::forward_iterator_tag ;
        using value_type = something ;
        using pointer = const something* ;
        using reference = const something& ;
        using difference_type = map_type::iterator::difference_type ;

        reference operator* () const { return internal_iterator->second ; }
        pointer operator& () const { return std::addressof(**this) ; }
        iterator& operator++ () { ++internal_iterator ; return *this ; }
        iterator operator++ (int) { const iterator ov = *this ; ++*this ; return ov ; }
        bool operator== ( const iterator& that ) const noexcept { return internal_iterator == that.internal_iterator ; }
        bool operator!= ( const iterator& that ) const noexcept { return !( *this == that ) ; }

        private:
            map_type::const_iterator internal_iterator ;
            iterator( map_type::const_iterator iter ) : internal_iterator(iter) {}
            friend holder ;
    };

    iterator begin() const noexcept { return iterator{ objects.begin() } ; }
    iterator end() const noexcept { return iterator{ objects.end() } ; }

    holder& add( something s ) { objects.emplace( ++next_id, std::move(s) ) ; return *this ; }

    private:
        map_type objects ;
        static id_type next_id ;
};

unsigned long long holder::next_id = 0 ;

int main()
{
    holder my_objects ;
    my_objects.add( {"abcd"} ).add( {"efgh"} ).add( {"ijkl"} ).add( {"mnop"} ).add( {"qrst"} ) ;
    for( const something& thing : my_objects ) std::cout << '{' << thing.str << "}\n" ;
}

http://coliru.stacked-crooked.com/a/22707053cffa8c6b
Very nice ( I keep failing to get clever with iterators ; I just do not think that way), but to show what I was saying about being vulnerable to pointer hand waving... The offset (48) that works locally for me is wrong for the shell compiler, so you have to re-hack it to find the new offset for this compiler, but on mine the output is shown..

this is not a lot of effort to do (much more if its compiled int a lib with the guts hidden), and there isn't much you can do about it beyond some sort of encryption, if it matters to you. One of the flaws, or powerful tools, available in c++ is the ability to use pointers to do low level tasks, and unfortunately a side effect of that is the ability to circumvent normal OOP data protections.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <iostream>
#include <map>
#include <string>

struct something { std::string str ; } ;

struct holder
{
    using id_type = unsigned long long ;
    using map_type = std::map<id_type,something> ;
    struct iterator
    {
        using iterator_category = std::forward_iterator_tag ;
        using value_type = something ;
        using pointer = const something* ;
        using reference = const something& ;
        using difference_type = map_type::iterator::difference_type ;

        reference operator* () const { return internal_iterator->second ; }
        pointer operator& () const { return std::addressof(**this) ; }
        iterator& operator++ () { ++internal_iterator ; return *this ; }
        iterator operator++ (int) { const iterator ov = *this ; ++*this ; return ov ; }
        bool operator== ( const iterator& that ) const noexcept { return internal_iterator == that.internal_iterator ; }
        bool operator!= ( const iterator& that ) const noexcept { return !( *this == that ) ; }

        private:
            map_type::const_iterator internal_iterator ;
            iterator( map_type::const_iterator iter ) : internal_iterator(iter) {}
            friend holder ;
    };

    iterator begin() const noexcept { return iterator{ objects.begin() } ; }
    iterator end() const noexcept { return iterator{ objects.end() } ; }

    holder& add( something s ) { objects.emplace( ++next_id, std::move(s) ) ; return *this ; }

    private:
        map_type objects ;
        static id_type next_id ;
};

unsigned long long holder::next_id = 0 ;

int main()
{
    holder my_objects ;
    my_objects.add( {"abcd"} ).add( {"efgh"} ).add( {"ijkl"} ).add( {"mnop"} ).add( {"qrst"} ) ;
    for( const something& thing : my_objects ) std::cout << '{' << thing.str << "}\n" ;
	
	std::map<unsigned long long,something>* pm;
	unsigned char * cp = (unsigned char*)(&my_objects);
	cp += sizeof(my_objects);
	cp-= 48; //bit of hackery needed to find this, may depend on compiler..
	pm = (std::map<unsigned long long,something>*) cp;	
	for(auto a: *pm)
	 std::cout<< a.first << " " << a.second.str <<  std::endl;
}



C:\c>a
{abcd}
{efgh}
{ijkl}
{mnop}
{qrst}
1 abcd
2 efgh
3 ijkl
4 mnop
5 qrst


Last edited on
The most simple approach would be an index:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Holder
{
  
  private:
    using ID = uint8_t;    
    std::map<ID, Something> obs;
  
  public:
  
    void addSomething(Something sthing)
    {
        ID randomID = generateFreeId(); // generate an ID   
        obs.emplace(id, sthing);
    }
    
    size_t begin()
    {
        return 0;
    }
    
    size_t end()
    {
        return obs.size();

    }
    
    Something* getSomething(const size_t idx)
    {
        size_t i = 0;
        for(std::map<ID, Something>::iterator it = obs.begin(); it != obs.end(); ++it)
        {
          if(i < idx)
            ++i;
          else
            return &(it->second);
        }
        return nullptr;
    }
};
Not tested!
jonnin wrote:
this is not a lot of effort to do (much more if its compiled int a lib with the guts hidden), and there isn't much you can do about it beyond some sort of encryption, if it matters to you.

Herb Sutter did list some ways to abuse code in http://www.gotw.ca/gotw/076.htm

He has also written about the pimpl idiom that can also hide guts into lib (although is more for "compilation firewalls").
> The offset (48) that works locally for me is wrong for the shell compiler, so you have to re-hack it
> to find the new offset for this compiler, but on mine the output is shown.

The iterator ought to be a standard layout class type; so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <iostream>
#include <map>
#include <string>
#include <type_traits>

struct something { std::string str ; } ;

struct holder
{
    using id_type = unsigned long long ;
    using map_type = std::map<id_type,something> ;
    struct iterator
    {
        using iterator_category = std::forward_iterator_tag ;
        using value_type = something ;
        using pointer = const something* ;
        using reference = const something& ;
        using difference_type = map_type::iterator::difference_type ;

        reference operator* () const { return internal_iterator->second ; }
        pointer operator& () const { return std::addressof(**this) ; }
        iterator& operator++ () { ++internal_iterator ; return *this ; }
        iterator operator++ (int) { const iterator ov = *this ; ++*this ; return ov ; }
        bool operator== ( const iterator& that ) const noexcept { return internal_iterator == that.internal_iterator ; }
        bool operator!= ( const iterator& that ) const noexcept { return !(*this==that) ; }

        private:
            map_type::const_iterator internal_iterator ;
            iterator( map_type::const_iterator iter ) : internal_iterator(iter) {}
            friend holder ;
    };

    iterator begin() const noexcept { return iterator{ objects.begin() } ; }
    iterator end() const noexcept { return iterator{ objects.end() } ; }

    holder& add( something s ) { objects.emplace( ++next_id, std::move(s) ) ; return *this ; }

    private:
        map_type objects ;
        static id_type next_id ;
};

unsigned long long holder::next_id = 0 ;

int main()
{
    holder my_objects ;
    my_objects.add( {"abcd"} ).add( {"efgh"} ).add( {"ijkl"} ).add( {"mnop"} ).add( {"qrst"} ) ;
    for( const something& thing : my_objects ) std::cout << '{' << thing.str << "}\n" ;

    // if the custom iterator is a standard-layout class type
    // (it would be unless the map's iterator unexpectedly happens to be a non-standard-layout type)
    if constexpr( std::is_standard_layout<holder::iterator>::value )
    {
        for( auto iter = my_objects.begin() ; iter != my_objects.end() ; ++iter )
        {
            const auto address = std::addressof(iter) ;

            // A pointer to an object of standard-layout class type can be reinterpret_cast to a
            // pointer to its first non-static non-bitfield data member
            // (padding is not allowed before the first (non-static non-bitfield) data member of a standard-layout type.)
            const auto internal_iter = *reinterpret_cast< const holder::map_type::const_iterator* >(address) ;
            std::cout << internal_iter->first << " -- {" << internal_iter->second.str << "}\n" ; // *** UB ***
        }
    }
}

http://coliru.stacked-crooked.com/a/7eb33bd84c0a900c
Right, very nice! I treated it as if I knew nothing (apart from the map template type, which I think can be fetched with RTTI?) about the target, and for some reason that made sense at the time but eludes me now, I went through it backwards.
Topic archived. No new replies allowed.