Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the C++ way to do this instead of using function pointers?

Tags:

c++

I am working on a part of a program that applies different types of filters onto an already read data part of a bitmap image. The method in question gets the data stored in a 2-dim std::vector and furthermore a pointer to the function in which the filter is applied as arguments. By that we can generically apply different filters by using this method. My question is, are function pointers the only way to achieve this, or does C++ offer a more beautiful and more readable solution to achieve it?

This is the method this question is about. Second argument is the function pointer that is used to access the function in the if/else statement inside the for loops.

void SteganoMessage::genFilter(std::vector<std::vector<uint32_t>> *d, uint32_t (*f)(uint32_t, size_t)){
    int count = 0;
    int pixel = getPixel();
    for(auto itOuter = d->begin(); itOuter != d->end(); ++itOuter){
        for(auto itInner = itOuter->begin(); itInner != itOuter->end(); ++itInner){
            if(mode == true)
                *itInner = f(*itInner, sizeof(*itInner));
            else
                *itInner = f(*itInner, this->getImage()->getBitmapHeader()->getBitCount()/8);
            displayProgress(count, pixel);
        }
    }
    displayProgress(0);
}

Call of genFilter function:

//...
{
    genFilter(data, substB);
}
//...

While substB is a function of course.

Would be very thankful for a hint that leads me into the right direction where I could research or a code snippet that shows a possible more C++ like way to do it.

like image 794
Florian Leuze Avatar asked Dec 13 '22 11:12

Florian Leuze


1 Answers

Type-preserving

The usual way to pass a function (or things that can be INVOKEd) in C++ is by using a template parameter:

// version #1
template <typename F>
void func(F f)
{
    static_assert(std::is_invocable_v<F, std::uint32_t, std::size_t>);

    // instead of f(*itInner, sizeof(*itInner))
    std::invoke(f, *itInner, sizeof(*itInner));
}

You can also use SFINAE to prevent postponing the error to instantiation time. This also enables overloading:

// version #2
template <typename F>
std::enable_if_t<std::is_invocable_v<F, std::uint32_t, std::size_t>>
    func(F f)
{
    // no need to static_assert here
    std::invoke(f, *itInner, sizeof(*itInner));
}

Since C++20, we can use concepts:

// version #3
template <std::Invocable<std::uint32_t, std::size_t> F>
void func(F f)
{
    // same as above
}

Which can be simplified further, using an abbreviated function template, to:

// version #4
void func(std::Invocable<std::uint32_t, std::size_t> auto f)
{
    // same as above
}

(This is still a function template rather than an ordinary function. It is equivalent to version #3.)

Type-erasing

You can also use std::function for type erasure:

// version #5
void func(std::function<void(std::uint32_t, std::size_t)> f)
{
    // ...
    f(*itInner, sizeof(*itInner));
}

Compared to the type-preserving alternatives (versions #1–4), this approach may reduce code bloat, but may incur runtime overhead for virtual function calling.

like image 120
L. F. Avatar answered Dec 28 '22 23:12

L. F.