Skip to content

make_const_map - #1483

Draft
mike919192 wants to merge 7 commits into
ETLCPP:masterfrom
mike919192:const_map_is_sorted
Draft

make_const_map#1483
mike919192 wants to merge 7 commits into
ETLCPP:masterfrom
mike919192:const_map_is_sorted

Conversation

@mike919192

Copy link
Copy Markdown
Contributor

Proposed solution to #1341 .

Adds make_const_map and make_const_map_with_comparer functions for assisting with constructing the class. The main thing that they provide is a static_assert that elements provided are sorted. As noted in the issue, the map does not function properly if the elements are not sorted.

What I found from research is that with the existing const_map constructor, it is not possible to use the variadic function parameter values at compile time in order to check for sorted. In order to check at compile time the elements must be variadic template parameters.

Because this uses auto template parameters this requires C++20. Also the gcc builds are failing, it seems due to a bug where before gcc12 where std::pair value was not able to be used as template parameter (https://stackoverflow.com/questions/64931324/what-is-the-purpose-of-having-an-empty-pair-base-class). This is working in newer gcc versions, but if this is acceptable than the older gcc version would need to be excluded.

I'll open this up as draft PR to get feedback on the API and implementation.

@rolandreichweinbmw

Copy link
Copy Markdown
Collaborator

This is a valid approach, and we can certainly think about updating the CI from using ubuntu-22.04 to ubuntu-24.04 for the relevant workflows (for C++23 we actually already did!).

However, this can be implemented for C++14 instead of requiring C++20, opening the feature to many more ETL users currently.

E.g. instead of going through template arguments for sort-check, you can make use of the const_map constructor which is already ETL_CONSTEXPR14. Then, you can make the constructor itself ill-formed during constant evaluation when the elements aren't sorted.

Basic idea:

namespace private_const_map
{
  inline void const_map_elements_are_not_sorted() {}   // deliberately NOT constexpr
}

template <typename... TElements>
ETL_CONSTEXPR14 explicit const_map(TElements&&... elements) ETL_NOEXCEPT
  : ...
{
  static_assert(...);                                  // existing asserts
  if (!elements_are_sorted())                          // ETL_CONSTEXPR14 loop over element_list
  {
    private_const_map::const_map_elements_are_not_sorted();
  }
}

@rolandreichweinbmw

Copy link
Copy Markdown
Collaborator

You can use this demo as a reference:

#include <etl/const_map.h>

namespace etl_priv
{
  inline void const_map_elements_are_not_sorted() {}   // non-constexpr -> not usable in a constant expression
}

template <typename TKey, typename TMapped, size_t Size, typename TKeyCompare = etl::less<TKey> >
class checked_const_map : public etl::const_map<TKey, TMapped, Size, TKeyCompare>
{
public:
  using base_t = etl::const_map<TKey, TMapped, Size, TKeyCompare>;
  using value_type = typename base_t::value_type;

  template <typename... TElements>
  ETL_CONSTEXPR14 explicit checked_const_map(TElements&&... elements)
    : base_t(etl::forward<TElements>(elements)...)
  {
    if (!is_sorted(this->begin(), this->end()))
    {
      etl_priv::const_map_elements_are_not_sorted();
    }
  }

private:
  static ETL_CONSTEXPR14 bool is_sorted(const value_type* first, const value_type* last)
  {
    TKeyCompare compare;
    if (first == last) return true;
    const value_type* previous = first++;
    while (first != last)
    {
      if (!compare(previous->first, first->first)) return false;
      previous = first++;
    }
    return true;
  }
};

using vt = ETL_OR_STD::pair<const int, int>;

constexpr checked_const_map<int, int, 3> good{ vt{1,1}, vt{2,2}, vt{3,3} };

#ifdef BAD
constexpr checked_const_map<int, int, 3> bad{ vt{3,3}, vt{2,2}, vt{1,1} };
#endif

int main() { return good.size() == 3 ? 0 : 1; }

@rolandreichweinbmw

Copy link
Copy Markdown
Collaborator

BTW: Same applies to const_set, const_multimap, const_multiset

@mike919192

Copy link
Copy Markdown
Contributor Author

Thanks I'll check out that approach

@mike919192

mike919192 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

You can use this demo as a reference:

#include <etl/const_map.h>

namespace etl_priv
{
  inline void const_map_elements_are_not_sorted() {}   // non-constexpr -> not usable in a constant expression
}

template <typename TKey, typename TMapped, size_t Size, typename TKeyCompare = etl::less<TKey> >
class checked_const_map : public etl::const_map<TKey, TMapped, Size, TKeyCompare>
{
public:
  using base_t = etl::const_map<TKey, TMapped, Size, TKeyCompare>;
  using value_type = typename base_t::value_type;

  template <typename... TElements>
  ETL_CONSTEXPR14 explicit checked_const_map(TElements&&... elements)
    : base_t(etl::forward<TElements>(elements)...)
  {
    if (!is_sorted(this->begin(), this->end()))
    {
      etl_priv::const_map_elements_are_not_sorted();
    }
  }

private:
  static ETL_CONSTEXPR14 bool is_sorted(const value_type* first, const value_type* last)
  {
    TKeyCompare compare;
    if (first == last) return true;
    const value_type* previous = first++;
    while (first != last)
    {
      if (!compare(previous->first, first->first)) return false;
      previous = first++;
    }
    return true;
  }
};

using vt = ETL_OR_STD::pair<const int, int>;

constexpr checked_const_map<int, int, 3> good{ vt{1,1}, vt{2,2}, vt{3,3} };

#ifdef BAD
constexpr checked_const_map<int, int, 3> bad{ vt{3,3}, vt{2,2}, vt{1,1} };
#endif

int main() { return good.size() == 3 ? 0 : 1; }

I played with this a bit and it looks promising. I have a couple observations.

The existing is_valid function seems to work instead of introducing a new is_sorted function.

Calling the non constexpr const_map_elements_are_not_sorted function correctly causes an error for constexpr variables, but it does not cause an error for unsorted runtime variables. For that, I think we can also add a runtime ETL_ASSERT, and that seems like it would be reasonably covered.

I'm not sure why this only works with a derived class checked_const_map and not with const_map itself. When I try to do any of this in the const_map constructor I get a bunch of "not constant expression" errors but it works in the checked_const_map constructor. I'm guessing it would be preferred to get it working with the existing classes.

Edit: OK now I got it working with the regular const_map class. I'll continue cleaning it up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants