1
\$\begingroup\$

Referring to https://codereview.stackexchange.com/a/281185/266548. I'd like to iterate, every frame, over the values. Do you think is a good idea to create std::vector<Property*> values_iterator? Or there is a better way to do it?

#include <algorithm>
#include <any>
#include <map>
#include <set>
#include <string>
#include <vector>
class Property
{
    std::any value_ = {};
    std::string name_ = {};
    std::size_t suffix_ = {};

public:
    Property(std::string name, std::size_t suffix, std::any value)
        : value_{std::move(value)},
          name_{std::move(name)},
          suffix_{suffix}
    {
    }
    Property() = default;

    auto name() const
    {
        if (suffix_) {
            return name_ + '(' + std::to_string(suffix_) + ')';
        }
        return name_;
    }

    auto const& value() const
    {
        return value_;
    }

    auto& value()
    {
        return value_;
    }

    bool isValid() const
    {
        return value_.has_value() && !name_.empty();
    }

    friend class PropertySet;
};
class PropertySet
{
    std::map<std::string, std::set<std::size_t>> suffixes_used = {};
    std::map<std::string, Property> values = {};
    std::vector<Property*> values_iterator = {};

public:
    template<typename T>
    Property& Add(std::string name, T value)
    {
        // choose first unused suffix
        auto& suffixes = suffixes_used[name];
        auto const suffix = next_suffix(suffixes);
        auto p = Property{std::move(name), suffix, std::move(value)};
        suffixes.insert(suffix);
        auto& property = values[p.Name()] = p;
        values_iterator.push_back(&property);
        return property;
    }

    void Remove(std::string name)
    {
        auto property = values.at(name);
        auto& suffixes = suffixes_used[property.name_];
        suffixes.erase(property.suffix_);
        if (suffixes.empty()) {
            suffixes_used.erase(property.name_);
        }
        values.erase(name);
        std::erase(values_iterator, &property);
    }

    const std::vector<Property*>& getValues() const
    {
        return values_iterator;
    }

private:
    static std::size_t next_suffix(const std::set<std::size_t>& s)
    {
        if (s.empty()) { return 0; }
        auto predicate = [](auto a, auto b){ return a + 1 != b; };
        if (auto it = std::ranges::adjacent_find(s, predicate); it != s.end()) {
            return *it + 1;
        }
        return *s.rbegin() + 1;
    }
};
```
\$\endgroup\$
0

1 Answer 1

3
\$\begingroup\$

Note that the PropertySet implementation causes ambiguity if a property has an integer suffix as a part of the name, e.g.

PropertySet ps;
ps.Add("a", 0); // a: 0
ps.Add("a", 1); // a(1): 1
ps.Add("a(1)", 2); // a(1): 2

The problem becomes substantial if the PropertySet is used together with GUI. And I don't see any simple solution that doesn't require parsing the property name.

Also, note that we need to do something with terminating spaces, e.g. "a****" and "a" are different properties now, considering "*" as a space character.


As for the question, there are multiple approaches to make PropertySet class iterable:

  • It can be thought as a values' proxy class. Essentially, we have added functionality to the map::insert and map::erase methods only. In that sense, we can add transparent begin and end methods. It only depends on the degree of compatibility with STL.

  • We can add a Java-like iterator class.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.