0

I'm writing a large library of functions that act on IShellItemArrays. Practically all of the functions need to access each IShellItem (and more specifically, each IShellItem2) in the array individually. So unless I'm overlooking something in the documentation, I believe I have to do something like this:

void SomeFunc(IShellItemArray* psia)
{
    HRESULT hr;

    DWORD cItems;
    hr = psia->GetCount(&cItems);
    if (FAILED(hr) || cItems == 0)
    {
        return;
    }

    for (UINT i = 0; i < cItems; ++i)
    {
        CComPtr<IShellItem> pShellItem;
        hr = psia->GetItemAt(i, &pShellItem);
        if (FAILED(hr))
        {
            continue;
        }

        CComPtr<IShellItem2> pShellItem2;
        pShellItem->QueryInterface(&pShellItem2);
        if (FAILED(hr))
        {
            continue;
        }

        // ...
    }
}

Now I'm trying to abstract that iteration away so I don't have to write it every time. So far I've tried to create a ForEachShellItem helper function that does the iteration and applies a desired function to each item, like this:

void ForEachShellItem(IShellItemArray* psia, HRESULT(*fn)(IShellItem2*))
{
    HRESULT hr;

    DWORD cItems;
    hr = psia->GetCount(&cItems);
    if (FAILED(hr) || cItems == 0)
    {
        return;
    }

    for (UINT i = 0; i < cItems; ++i)
    {
        CComPtr<IShellItem> pShellItem;
        hr = psia->GetItemAt(i, &pShellItem);
        if (FAILED(hr))
        {
            continue;
        }

        CComPtr<IShellItem2> pShellItem2;
        pShellItem->QueryInterface(&pShellItem2);
        if (FAILED(hr))
        {
            continue;
        }

        fn(pShellItem2);

    }
}

The problem is that if the necessary function is of a different signature than that function pointer parameter, then that won't work. So is there a way to generalize or templatize this approach? Or is there any other strategy to avoid repetition of the iteration code? Thanks for any input.

2 Answers2

2

There are things you can do with std::function and a capturing lambda. Thus, given:

void ForEachShellItem(IShellItemArray*, std::function <HRESULT (IShellItem2 *)> fn)
{
    ...
    fn (si);
}

Then to pass an additional parameter to your lambda, you can do:

void ForEachShellItem(IShellItemArray *isa, std::function <HRESULT (IShellItem2 *psi)> fn)
{
    ...
    HRESULT hr = fn (psi);
}

IShellItemArray isa = /* ... */;
int additional_param = 42;
ForEachShellItem (&isa, [additional_param] (IShellItem2 *psi)
    { std::cout << additional_param; return 0; });

And to return an additional return value, you can do:

IShellItemArray isa = /* ... */;
int additional_return_value = 0;
ForEachShellItem (&isa, [&additional_return_value] (IShellItem2 *psi)
    { additional_return_value = 43; return 0; });
std::cout << additional_return_value << "\n";

Live demo

You can also pass additional parameters and return values via a template. For example:

template <typename F, typename ... Args>
void ForEachShellItem(IShellItemArray*, F fn, Args && ... args)
{
    ...
    fn (si, std::forward <Args> (args)...);
}

IShellItemArray isa = /* ... */;
int additional_return_value = 0;
ForEachShellItem (&isa, [] (IShellItem2 *, int additional_param, int &additional_return_value)
    { std::cout << additional_param << "\n"; additional_return_value = 43; return 0; },
    42, additional_return_value);
std::cout << additional_return_value << "\n";

Live demo

Paul Sanders
  • 24,133
  • 4
  • 26
  • 48
  • Is there any drawback to the template approach? It seems like you and Raymond favor the `std::function` approach, so perhaps I'll use that, but at least for now the template strategy seems more readable to me. –  Jun 22 '20 at 01:35
  • Also, can the `std::function` approach be used with a regular named function, instead of a lambda? –  Jun 22 '20 at 14:10
  • Yes, that should work, but the advantage of the lambda is that you can capture variables from its enclosing scope as I show in my examples. – Paul Sanders Jun 22 '20 at 19:02
  • Sorry, just saw your first question. The template approach has the disadvantage that each different instantiation of the template duplicates the code. But that probably doesn't matter much and as you say it's more convenient, and it too can be used with regular functions. – Paul Sanders Jun 22 '20 at 20:09
  • @loop123123 I did not intend to favor one over the other. I said "you can use A or B." – Raymond Chen Jun 26 '20 at 14:39
0

Here is a non-template approach.

You can implement the template design pattern, along with overloading the call operator, operator().

Here is an example:

#include <iostream>

class Base
{
public:
    virtual ~Base() {}
    virtual void operator()() {}
    void GenericCaller()  // This would be your SomeFunc
    {
        std::cout << "this part is generic\n";

        operator()();  // This would be the custom function call
    }
};

class Derived : public Base
{
    int parm1, parm2;
public:
    Derived(int p1, int p2) : parm1(p1), parm2(p2) {}
    void operator()()
    {
        std::cout << "From Derived: " << parm1 << " " << parm2 << "\n";
    }
};

class Derived2 : public Base
{
    int parm1;
public:
    Derived2(int p1) : parm1(p1) {}
    void operator()()
    {
        std::cout << "From Derived2: " << parm1 << "\n";
    }
};

void caller(Base& b)
{
    b.GenericCaller();
}

int main()
{
    Derived d1(1, 2);
    Derived2 d2(3);
    caller(d1);
    caller(d2);
}

Output:

this part is generic
From Derived: 1 2
this part is generic
From Derived2: 3

The way this works is that the GenericCaller is common to all classes, thus will be always invoked. Note that at the end, the derived's call operator is invoked.

The magic is that the parameter lists are moved away from the call site and into the derived class constructor. Note that Derived1 and Derived2 have differing "parameter lists".

PaulMcKenzie
  • 34,698
  • 4
  • 24
  • 45
  • Well, with this approach, a named function (for ex. `ProcessShelItem`) might be a better choice as one might implement the callback into an existing class and make the code more readable. – Phil1970 Jun 22 '20 at 01:31
  • Note that this is basically reinventing `std::function` but with some hidden pitfalls (e.g., if you copy the `Base`, you get a slice, which will not end well). – Raymond Chen Jun 26 '20 at 14:43