Get total number of elements in a nested STL-like container

369 views Asked by At

I would like to write a C++ function that can count the total number of "atomic" elements in a generic nested STL-like container, with the following conditions:

  1. Each level may be any type of container.

  2. The number of levels is not given a priori.

I wrote the following recursive function (using code from here):

template <typename T>
size_t get_total_size(const T & var)
{
    if ( is_container<typeof(var)>::value ) {  // From https://stackoverflow.com/a/9407521/2707864
        typename T::const_iterator iter;
        size_t sumsize = 0;
        for ( iter = var.begin() ; iter!= var.end() ; iter++ ) {
            sumsize += get_total_size(*iter);
        }
        return sumsize;
    } else {
        return 1;
    }
};

Compiling/linking this may work. The problem is that upon using it (otherwise, there wouldn't be any point in writing it!) does not compile, as the instantiation gets at the "atomic" level to a type that does not have iterators, e.g., in this code

typedef vector<int> vint;
typedef vector<vint> vvint;
vvint vec_heap;
for (int i=0; i < 12; i++) {
    vec_heap.push_back(vint(2, i));
}
cout << get_total_size(vec_heap) << endl;  // Instantiation leads to compilation errors 

Is this possible?

EDIT: As per one comment, it can be done with c++17... Is the recursive function I wrote an overkill for the objective?

2

There are 2 answers

5
Jarod42 On BEST ANSWER

With C++17, you might use if constexpr:

template <typename T>
size_t get_total_size(const T& var)
{
    if constexpr (is_container<T>::value) {
        return std::accumulate(var.begin(),
                               var.end(),
                               0u,
                               [](int acc, const auto& e){ return acc + get_total_size(e); });
    } else {
        return 1u;
    }
};

Before that, you might use overloads and SFINAE:

// this will be called when T is not a container (it is the "atomic" type)
template <typename T, std::enable_if_t<!is_container<T>::value, int> = 0>
size_t get_total_size(const T& var)
{
    return 1u;
};
// this will be called for all container types, except for maps
template <typename T, std::enable_if_t<is_container<T>::value, int> = 0>
size_t get_total_size(const T& var)
{
    return std::accumulate(var.begin(),
                           var.end(),
                           0u,
                           [](int acc, const auto& e){ return acc + get_total_size(e); });
};
// this will be called for maps
template <typename Key, typename T>
size_t get_total_size(const std::map<Key, T> & var)
{
    return std::accumulate(var.begin(),
                           var.end(),
                           0u,
                           [](int acc, const auto& e){ return acc + get_total_size_sfinae(e.second); });
}
7
NathanOliver On

If you can't use C++17, or just want to open up what standard can be used with your function then you can switch to using two overloads and use SFINAE to determine when to call each overload. Using

// this will be called when T is not a container (it is the "atomic" type)
template <typename T, typename std::enable_if<!is_container<T>::value, bool>::type = true>
size_t get_total_size(const T & var)
{
    return 1;
}

// forward declare of pair function for associative containers
template <typename T, typename U>
size_t get_total_size(const std::pair<T, U> & var);

// this will be called for all container types
template <typename T, typename std::enable_if<is_container<T>::value, bool>::type = true>
size_t get_total_size(const T & var)
{
    size_t sumsize = 0;
    for ( auto iter = var.begin() ; iter != var.end() ; ++iter ) {
        sumsize += get_total_size(*iter);
    }
    return sumsize;
}

// this will be called for pair to handle associative containers
template <typename T, typename U>
size_t get_total_size(const std::pair<T, U> & var)
{
    return get_total_size(var.first) + get_total_size(var.second);
}

This will work from C++11 on up which you can see in this live example