Gilad
Gilad

Reputation: 6595

C++ clamp function for a std::vector

void Clamp(std::vector<double>& input_image, double min_value, double max_value, int width, int height)
{
    for (int j = 0; j < width; j++)
    {
        for (int i = 0; i < height; i++)
        {
            input_image[i*width + j] = std::min(std::max(input_image[i*width + j], min_value), max_value);
        }
    }
}

I am trying to create this functionality of clamping.

Is there a better way to do it using a std::vector?

I can also use C++11

Upvotes: 2

Views: 3787

Answers (4)

Felix Glas
Felix Glas

Reputation: 15534

C++17 has std::clamp. Also there's no need for the height and width parameters in this case.

void clamp_vec(std::vector<double>& input_image, double min_value, double max_value) {
    std::transform(std::begin(input_image), std::end(input_image), std::begin(input_image),
                   [=] (auto i) { return std::clamp(i, min_value, max_value); });
}

Template version for generic types will work as long as operator < is defined:

template <typename T>
void clamp_vec(std::vector<T>& input_image, const T& min_value, const T& max_value) {
    std::transform(std::begin(input_image), std::end(input_image), std::begin(input_image),
                   [&] (const T& v) { return std::clamp(v, min_value, max_value); });
}

If std::clamp is not available, you can implement your own until C++17 comes around, e.g.:

template <typename T>
constexpr const T& clamp(const T& v, const T& lo, const T& hi) {
    return clamp(v, lo, hi, std::less<>());
}

template <typename T, typename Compare>
constexpr const T& clamp(const T& v, const T& lo, const T& hi, Compare comp) {
    return comp(v, lo) ? lo : comp(hi, v) ? hi : v;
}

Upvotes: 4

Richard Hodges
Richard Hodges

Reputation: 69902

I think I'd just do it in one pass and let the optimiser do its work.

void Clamp(std::vector<double>& input_image, double min_value, double max_value)
{
    auto clamp = [min_value, max_value](double x) {
        return std::min(std::max(x, min_value), max_value);
    };

    std::transform(input_image.begin(), input_image.end(),
                   input_image.begin(), clamp);
}

testing this on godbolt, gcc vectorised it and completely eliminated the redundant copy of clamp.

Upvotes: 2

Jonas
Jonas

Reputation: 7017

You could use std::transform, assuming that you apply your function to every element then there is no need for the height and width parameters. Splitting the min and max operations seems more SIMD friendly, thus I suggest:

void Clamp(std::vector<double>& input_image, double min_value, double max_value)
{
    std::transform(std::begin(input_image), std::end(input_image), std::begin(input_image), [max_value] (double d) { return std::min(d, max_value); }
    std::transform(std::begin(input_image), std::end(input_image), std::begin(input_image), [min_value] (double d) { return std::max(d, max_value); }
}

Otherwise a C++11 way could be to use a range-based for loop:

void Clamp(std::vector<double>& input_image, double min_value, double max_value) // removed hight and width
{
    for (double& d : input_image)
    {
        d = std::min(std::max(d, min_value), max_value);
    }
}

Or the more SIMD "friendly":

void Clamp(std::vector<double>& input_image, double min_value, double max_value) // removed height and width
{
    for (double& d : input_image)
    {
        d = std::max(d, min_value);
    }
    for (double& d : input_image)
    {
        d = std::min(d, max_value);
    }
}

Upvotes: 2

The Quantum Physicist
The Quantum Physicist

Reputation: 26336

There you go. I made two transforms just to give the compiler a better chance for optimizations and vectorization. You can join them if you wish in 1 lambda if you disagree.

std::transform(input_image.begin(), input_image.end(), input_image.begin(), [lo](double v) {return (v < lo ? lo : v);});
std::transform(input_image.begin(), input_image.end(), input_image.begin(), [hi](double v) {return (v > hi ? hi : v);});

Always keep in mind that looping over consecutive memory elements is much faster. Refer to std::transform reference to understand how it works.

Upvotes: 1

Related Questions