2

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
    Do you have access to `C++17`? Replacing `if ( is_container::value )` with `if constexpr ( is_container::value )` would solve the issue. Otherwise you are left with metaprogramming, I believe. – Fureeish Feb 06 '20 at 22:11
  • @Fureeish - I cannot guarantee all users of this code will have that. I just tested your suggestion and it works. You may post it as an answer, it is quite instructive. – sancho.s ReinstateMonicaCellio Feb 06 '20 at 22:16

2 Answers2

2

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); });
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302
1

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

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • I am getting quite a few errors, even with the `#include`s from your live example. **1.** `expected identifier before '::' token` (line 2) **2.** `expected unqualified-id before '=' token` (line 2) **3.** `redefinition of 'template > size_t get_total_size_sfinae2(const T&)'` (line 9) **4.** `template argument 1 is invalid` (line 2) etc... probably all stemming from the same silly issue. – sancho.s ReinstateMonicaCellio Feb 06 '20 at 23:24
  • @sancho.sReinstateMonica Can you make a [mre] and share a link to it? It works fine in my example. – NathanOliver Feb 06 '20 at 23:30
  • @sancho.sReinstateMonica Aha. I had `typename std::enable_if<!is_container, bool>::type` in my answer, but had `typename std::enable_if<!is_container::value, bool>::type` in my live example. I've updated the answer to match and now it works. New live example for you: http://coliru.stacked-crooked.com/a/a1622e9b7115a97e – NathanOliver Feb 07 '20 at 13:15
  • @sancho.sReinstateMonica You can use http://coliru.stacked-crooked.com/ for free to compile using g++ or clang++ and if you hit share, it will turn the link in the address bar into a permenate link you can copy and share. – NathanOliver Feb 07 '20 at 13:16
  • @sancho.sReinstateMonica I've changed you update so that it will work for all associative containers, not just `std::map`. – NathanOliver Feb 07 '20 at 19:45
  • Is *sum* the appropriate operation? – sancho.s ReinstateMonicaCellio Feb 07 '20 at 20:34
  • @sancho.sReinstateMonica IDK. That's up to you. I did a sum because you could have a container as the key and or value of the map. – NathanOliver Feb 07 '20 at 20:35