Reputation: 3964
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:
SDK::revokeIPPoolFromNetworkInterface
make wrapperIPPool
with overloads for both types and call SDK::revokeIPPoolFromNetworkInterface
internally.revokeIPPool
for both types (but it's code duplication, no improvement against original code)revokeIPPool
(is this possible?)revokeIPPool
revokeIPPool
in class and make partial class template specialization.Questions:
Upvotes: 4
Views: 625
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
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
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