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.
The usual way to pass a function (or things that can be INVOKE
d) 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.)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With