likern
likern

Reputation: 3964

Ways to generalize C++ code with different similar types

Currently I have the following code:

static void markPoolsFree(const TNetgroupPools &group_info, TObjectID netiface) {
    for (size_t i = 0; i<group_info.public_pools.length();i ++ ) {
        SDK::revokeIPPoolFromNetworkInterface(group_info.public_pools[i],netiface);
    }

    for (size_t i =0 ; i<group_info.private_pool.length(); i++) {
        SDK::revokeIPPoolFromNetworkInterface(group_info.private_pool[i].pool_id,
                                              netiface);
    }
}

which has basically the same logic, but differents in types group_info.public_pools[i] and group_info.private_pool[i], that's why in second loop we have to add .pool_id member call. These types are different and don't have any relationships.

I want to rewrite this code to make it more general, for example something like this (sketch):

// template function
template <typename Container, typename PredArgs>
static void revokeIPPool(const Container &pool, TObjectID netiface,
                         bool (*pred)(PredArgs pool_id, PredArgs netiface_id))
{
     for (size_t i = 0; i < pool.length(); ++i) {
         if (pred(pool[i], netiface)) {
             SDK::revokeIPPoolFromNetworkInterface(pool[i], netiface);
         }

     }
}

// calling code
static void markPoolsFree(const TNetgroupPools &group_info, TObjectID netiface) {
    revokeIPPool(group_info.public_pools, netiface, SDK::isPredicateTrue);
    revokeIPPool(group_info.private_pool, netiface, SDK::isPredicateTrue);
}

But the problem is in different types for public_pools and private_pool.

Question: Could you give all ways how is possible to generalize this code with examples? I need C++03 code, but C++11/C++14 is acceptable.

My thoughts:

  1. Instead of SDK::revokeIPPoolFromNetworkInterface make wrapperIPPool with overloads for both types and call SDK::revokeIPPoolFromNetworkInterface internally.
  2. Overload revokeIPPool for both types (but it's code duplication, no improvement against original code)
  3. Partial function template specialization for revokeIPPool (is this possible?)
  4. Full function template specialization for revokeIPPool
  5. Wrap function revokeIPPool in class and make partial class template specialization.

Questions:

  1. Wich of my thoughts are correct?
  2. What's about advantages and shortcomings?
  3. Which is more idiomatic for C++03 or C++11?
  4. Is there other solutions?

Upvotes: 4

Views: 625

Answers (3)

Michael Anderson
Michael Anderson

Reputation: 73520

In C++03 I'd use template specialization on a traits class. (The main reason for going through a traits class, rather than using direct function specialisation, or overload is that you can perform partial template specialisation for classes, but not functions - and while its not needed here, it may be something that is useful later )

static void markPoolsFree(const TNetgroupPools &group_info, TObjectID netiface) {
    markPoolsFreeImpl(group_info.public_pools, netiface);
    markPoolsFreeImpl(group_info.private_pools, netiface);
}

template<typename T>
static void markPoolsFreeImpl(POOLS pools, TObjectID netiface) {
    for (size_t i = 0; i<pools.length();i ++ ) {
       PoolId poolid = PoolListTrait<POOLS>::get(pools,i);
       SDK::revokeIPPoolFromNetworkInterface(poolid,netiface);
    }
}

template<typename T> class PoolListTrait {};

template<> class PoolListTrait<PublicPoolList> {
  static PoolId get(PublicPoolList pools, int i) { return pools[i]; }
}

template<> class PoolListTrait<PrivatePoolList> {
  static PoolId get(PrivatePoolList pools, int i) { return pools[i].pool_id; }
}

Upvotes: 3

Barry
Barry

Reputation: 303337

As we go from C++03 to C++11 to C++14, our solution gets shorter both in terms of being less total code and in terms of being able to be more localized - both clear wins. In C++14 we can do it in just one 10-line function, whereas in C++03 we needed 4 functions. Here's how I would do it in each of the three feature sets:


C++03: Functors! Basically how you were expected to use every standard algorithm back in the day (or free functions).

template <typename POOL, typename EXTRACTOR>
static void markPoolsFreeImpl(const POOL &pool, TObjectID netiface, 
    EXTRACTOR ex)
{
    for (std::size_t i = 0; i < pool.length(); ++i) {
        SDK::removeIPPoolFromNetworkInterface(ex(pool[i]), netiface);
    }
}


static void markPoolsFree(const TNetgroupPools &group_info, TObjectID netiface) {
    markPoolsFreeImpl(group_info.public_pools,
                  netiface,
                  PublicExtractor());

    markPoolsFreeImpl(group_info.private_pools,
                  netiface,
                  PrivateExtractor());
}

// fill in T, U, V as appropriate here, I dunno what they are
struct PublicExtractor {
    T operator()(const U& item) const { return item; }
};

struct PrivateExtractor {
    T operator()(const V& item) const { return item.pool_id; }
};

This could also look like:

T PublicExtractor(const U& item) { return item; }

And just pass it in without the extra ()s. If we accidentally provide markPoolsFreeImpl an EXTRACTOR with the wrong operator(), it just won't compile pointing to the line where it's called. This isn't great, but it's about as good as we can do.


C++11: Lambdas! The markPoolsFreeImpl function can look the same, it's just that instead of defining our functors wherever, we can definte them inline:

static void markPoolsFree(const TNetgroupPools &group_info, TObjectID netiface) {
    markPoolsFreeImpl(group_info.public_pools,
                  netiface,
                  [](const U& item) { return item; });

    markPoolsFreeImpl(group_info.private_pools,
                  netiface,
                  [](const V& item) { return item.pool_id; });
}

Furthermore, we can make our error messages clearer in case we pass the wrong kind of extractor function (again, fill in T as appropriate):

template <typename POOL, typename EXTRACTOR>
static void markPoolsFreeImpl(const POOL &pool, TObjectID netiface, 
    EXTRACTOR ex)
{
    static_assert(std::is_same<T, decltype(ex(pool[0]))>::value, 
                  "invalid EXTRACTOR: must return a T");

    for (std::size_t i = 0; i < pool.length(); ++i) {
        SDK::removeIPPoolFromNetworkInterface(ex(pool[i]), netiface);
    }
}

C++14: Simpler Lambdas! We don't even have to identify the U and V:

static void markPoolsFree(const TNetgroupPools &group_info, TObjectID netiface) {
    markPoolsFreeImpl(group_info.public_pools,
                  netiface,
                  [](const auto& item) { return item; });

    markPoolsFreeImpl(group_info.private_pools,
                  netiface,
                  [](const auto& item) { return item.pool_id; });
}

And can even turn markPoolsFreeImpl into a local lambda:

static void markPoolsFree(const TNetgroupPools &group_info, TObjectID netiface) {
    // netiface is captured
    auto impl = [&](const auto& pool, auto ex) {
        static_assert(std::is_same<T, decltype(ex(pool[0]))>::value, "");

        for (std::size_t i = 0; i < pool.length(); ++i) {
            SDK::removeIPPoolFromNetworkInterface(ex(pool[i]), netiface);
        }
    });

    impl(group_info.public_pools, [](const auto& item) { return item; });
    impl(group_info.private_pools, [](const auto& item) { return item.pool_id; });
}

Upvotes: 2

codestation
codestation

Reputation: 3498

Dunno if generic lambdas (C++14) are an acceptable solution for you:

auto revoke_pool = [](auto &pool, TObjectID netiface, auto extract_item)
{
    for(std::size_t i = 0; i < pool.length(); ++i)
        SDK::revokeIPPoolFromNetworkInterface(extract_item(pool, i), netiface);
};

Then you only hace to define a lambda to access the pool item.

revoke_pool(group_info.public_pools, netiface, [](auto &pool, std::size_t idx) { return pool[idx]; });
revoke_pool(group_info.private_pool, netiface, [](auto &pool, std::size_t idx) { return pool[idx].pool_id; });

Upvotes: 2

Related Questions