A M
A M

Reputation: 15265

What would be the C++20 concept for an iterable container?

I am still on C++17. I tried to write a generic code, to retrieve the "top count" element from any iterable container. But in my opinion this looks somehow clumsy.

Is there a better way to check the type traites in C++20 with concepts? How would that look like?

The requirements can be taken from the range based for as shown in cppreference and would be:

    auto && __range = range_expression ;
    auto __begin = begin_expr ;
    auto __end = end_expr ;
    for ( ; __begin != __end; ++__begin) {

        range_declaration = *__begin;
        loop_statement
    }

so, begin, end, ++, * and !=


I built a solution by defining a type trait using SFINAE mechanisms. Please see:

#include <iostream>
#include <utility>
#include <unordered_map>
#include <queue>
#include <vector>
#include <iterator>
#include <type_traits>
#include <string>


// Helper for type trait We want to identify an iterable container ----------------------------------------------------
template <typename Container>
auto isIterableHelper(int) -> decltype (
    std::begin(std::declval<Container&>()) != std::end(std::declval<Container&>()),     // begin/end and operator !=
    ++std::declval<decltype(std::begin(std::declval<Container&>()))&>(),                // operator ++
    void(*std::begin(std::declval<Container&>())),                                      // operator*
    void(),                                                                             // Handle potential operator ,
    std::true_type{});
template <typename T>
std::false_type isIterableHelper(...);

// The type trait -----------------------------------------------------------------------------------------------------
template <typename Container>
using is_iterable = decltype(isIterableHelper<Container>(0));

// Some Alias names for later easier reading --------------------------------------------------------------------------
template <typename Container>
using ValueType = std::decay_t<decltype(*std::begin(std::declval<Container&>()))>;
template <typename Container>
using Pair = std::pair<ValueType<Container>, size_t>;
template <typename Container>
using Counter = std::unordered_map<ValueType<Container>, size_t>;
template <typename Container>
using UnderlyingContainer = std::vector<Pair<Container>>;

// Predicate Functor
template <class Container> struct LessForSecondOfPair {
    bool operator () (const Pair<Container>& p1, const Pair<Container>& p2) { return p1.second < p2.second; }
};
template <typename Container>
using MaxHeap = std::priority_queue<Pair<Container>, UnderlyingContainer<Container>, LessForSecondOfPair<Container>>;


// Function to get most frequent used number in any Container ---------------------------------------------------------
template <class Container>
auto topFrequent(const Container& data) {

    if constexpr (is_iterable<Container>::value) {

        // Count all occurences of data
        Counter<Container> counter{};
        for (const auto& d : data) counter[d]++;

        // Build a Max-Heap
        MaxHeap<Container> maxHeap(counter.begin(), counter.end());

        // Return most frequent number
        return maxHeap.top().first;
    }
    else
        return data;
}
// Test
int main() {
    std::vector testVector{ 1,2,2,3,3,3,4,4,4,4,5,5,5,5,6,6,6,6,6,7 };
    std::cout << "Most frequent is: " << topFrequent(testVector) << "\n";

    double cStyleArray[] = { 1.1, 2.2, 2.2, 3.3, 3.3, 3.3 };
    std::cout << "Most frequent is: " << topFrequent(cStyleArray) << "\n";

    std::string s{ "abbcccddddeeeeeffffffggggggg" };
    std::cout << "Most frequent is: " << topFrequent(s) << "\n";

    double value = 12.34;
    std::cout << "Most frequent is: " << topFrequent(value) << "\n";

    return 0;
}

So, what would be the C++20 solution?


Developed and tested with Microsoft Visual Studio Community 2019, Version 16.8.2.

Additionally compiled and tested with clang11.0 and gcc10.2 with flags --std=c++17 -Wall -Wextra -Wpedantic

Language: C++17

Upvotes: 3

Views: 1607

Answers (1)

康桓瑋
康桓瑋

Reputation: 43036

Your ValueType trait basicly is std::ranges::range_value_t, and your is_iterable basically is concept std::ranges::range:

template <typename Container>
using ValueType = std::ranges::range_value_t<Container>;

template <class Container>
auto topFrequent(const Container& data) {
  if constexpr (std::ranges::range<Container>) {
    // Count all occurences of data
    Counter<Container> counter{};
    for (const auto& d : data) counter[d]++;

    // Build a Max-Heap
    MaxHeap<Container> maxHeap(counter.begin(), counter.end());

    // Return most frequent number
    return maxHeap.top().first;
  }
  else
    return data;
}

See Demo.

Upvotes: 2

Related Questions