Examples of Projections from C++20 Ranges

[ad_1]

C++20 Ranges algorithms have a wonderful characteristic referred to as “Projection”. In brief, it’s a callable utilized on every vary ingredient earlier than additional processing. On this article, I’ll present you a pair extra examples of this useful characteristic.

Intro

 

In keeping with the C++20 Customary: [defns.projection]:

projection: transformation that an algorithm applies earlier than inspecting the values of components

std::pair<int, std::string_view> pairs[] = {
    {2, "foo"}, {1, "bar"}, {0, "baz"}
};

std::ranges::kind(pairs, std::ranges::much less{}, [](auto const& p) {

return p.first; });

Above, now we have a projection that takes a pair after which extracts solely the primary member, and this member is used to carry out the type algorithm. By default, every algorithm makes use of id projection. For instance, right here’s an extract from copy_if:

constexpr copy_if_result<I, O>
    copy_if( I first, S final, O outcome, Pred pred, Proj proj = {} );

Such a projection is then invoked by std::invoke and helps the next transformations:

  • a lambda or different callable object
  • a pointer to a member operate
  • a pointer to information member

I described the way it works intimately in my different article: C++20 Ranges, Projections, std::invoke and if constexpr – C++ Tales

Let’s take a look at some examples to see this characteristic in motion.

Sorting

 

Let’s begin with one thing easy like sorting; we are able to increase the instance from the Customary:

#embody <algorithm>
#embody <ranges>
#embody <iostream>

int essential() {
    std::pair<int, std::string_view> pairs[] = {
        {2, "foo"}, {1, "bar"}, {0, "baz"}
    };

    // member entry:
    std::ranges::kind(pairs, std::ranges::much less{}, 
        &std::pair<int, std::string_view>::first);

    // a lambda:
    std::ranges::kind(pairs, std::ranges::much less{}, 
        [](auto const& p) { return p.first; });
}

Run @Compiler Explorer.

We are able to additionally attempt sorting constructions:

struct Field {
    std::string identify;    
    double w = 0.0;
    double h = 0.0;
    double d = 0.0;

    constexpr double quantity() const { return w*h*d; }
};

void print(std::string_view intro, const std::vector<Field>& container) {
    std::cout << intro << 'n';
    for (const auto &elem : container)
        std::cout << std::format("{}, quantity {}n", elem.identify, elem.quantity());
}

int essential() {
    const std::vector<Field> container {
        {"giant cubic", 10, 10, 10}, {"small cubic", 3, 3, 3},
        {"giant lengthy", 10, 2, 2}, {"small", 3, 2, 2}
    };
    
    print("preliminary", container);

    // the ranges model:
    auto copy = container;   
    std::ranges::kind(copy, {}, &Field::identify);    
    print("after sorting by identify", copy);           
    std::ranges::kind(copy, {}, &Field::quantity);    
    print("after sorting by quantity", copy);     
}

Run @Compiler Explorer

And right here’s the output:

preliminary
giant cubic, quantity 1000
small cubic, quantity 27
giant lengthy, quantity 40
small, quantity 12
after sorting by identify
giant cubic, quantity 1000
giant lengthy, quantity 40
small, quantity 12
small cubic, quantity 27
after sorting by quantity
small, quantity 12
small cubic, quantity 27
giant lengthy, quantity 40
giant cubic, quantity 1000

Within the instance, I used a easy construction after which handed a pointer to a knowledge member or a pointer to a member operate.

Remodel

 

See the code under:

#embody <algorithm>
#embody <vector>
#embody <iostream>
#embody <ranges>

struct Product {
    std::string name_;
    double value_ { 0.0 };
};

int essential() {
    std::vector<Product> prods{7, {"Field ", 1.0}};

    // normal model:  
    std::rework(start(prods), finish(prods), start(prods), 
        [v = 0](const Product &p) mutable {
            return Product { p.name_ + std::to_string(v++), 1.0};
        }
    );
    for (auto &p : prods) std::cout << p.name_ << ", ";
    std::cout << 'n';

    // ranges model:  
    std::ranges::rework(prods, start(prods), 
        [v = 0](const std::string &n) mutable {
            return Product { n + std::to_string(v++), 1.0};
        }, 
        &Product::name_);
    for (auto &p : prods) std::cout << p.name_ << ", ";
}

Play @Compiler Explorer.

The output:

Field 0, Field 1, Field 2, Field 3, Field 4, Field 5, Field 6, 
Field 00, Field 11, Field 22, Field 33, Field 44, Field 55, Field 66, 

The normal model of the std::rework creates names utilizing your complete Product& p parameter. Then the ranges model takes solely the string parameter and expands that identify. Discover the completely different parameters handed into the transformation lambda.

Max ingredient

 

In my article about std::format, I wanted to search out the longest string in a map of names:

const std::map<std::string, std::array<double, 5>> productToOrders{
        { "apples", {100, 200, 50.5, 30, 10}},
        { "bananas", {80, 10, 100, 120, 70}},
        { "carrots", {130, 75, 25, 64.5, 128}},
        { "tomatoes", {70, 100, 170, 80, 90}}
};

Initially, I had the next operate:

template <typename T>
size_t MaxKeyLength(const std::map<std::string, T>& m) {
    if (m.empty())
        return 0;
        
	auto res = std::ranges::max_element(std::views::keys(m), 
	[](const auto& a, const auto& b) {
		return a.size() < b.size();
		});
	return (*res).size();
}

The above code makes use of a customized comparator that appears at a given property from the keys view.

However we are able to change it to make use of a projection:

template <typename T>
size_t MaxKeyLength(const std::map<std::string, T>& m) {
    if (m.empty())
        return 0;
    auto res = std::ranges::max_element(std::views::keys(m), 
        std::much less{}, &std::string::size // <<
        );
    return (*res).size();
}

See this line:

std::much less{}, &std::string::size // <<

I’m utilizing a default comparator and a customized projection that could be a pointer to a member operate.

See extra articles about Ranges on the weblog:

Customized algorithm

 

Projection just isn’t reserved for Ranges. You may also use it in your code. Right here’s an instance with a operate that prints a container:

void PrintEx(const std::ranges::vary auto& container, auto proj) {
	for (size_t i = 0; auto && elem : container)
		std::cout << std::invoke(proj, elem) 
                  << (++i == container.dimension() ? "n" : ", ");
};

int essential() {
    std::vector<std::pair<int, char>> pairs { {0, 'A'}, {1, 'B'}, {2, 'C'}};
    PrintEx(pairs, &std::pair<int, char>::first);
    PrintEx(pairs, &std::pair<int, char>::second);
}

See @Compiler Explorer

The “magical” half is to make use of std::invoke on the proj parameter. And that’s all.

And we are able to additionally improve the code with a default template parameter:

template <typename Proj = std::id>
void PrintEx(const std::ranges::vary auto& container, Proj proj = {}) {
	for (size_t i = 0; auto && elem : container)
		std::cout << std::invoke(proj, elem) 
                  << (++i == container.dimension() ? "n" : ", ");
};

And now, we are able to write the next demo:

int essential() {
    std::vector<std::pair<int, char>> pairs { {0, 'A'}, {1, 'B'}, {2, 'C'}};
    PrintEx(pairs, &std::pair<int, char>::first);
    PrintEx(pairs, &std::pair<int, char>::second);

    std::vector numbers {1, 2, 3, 4, 5};
    PrintEx(numbers); // default proj
    PrintEx(numbers, [](auto& v) { return v*v;});

    std::map<std::string, int> phrases { {"Good day", 1}, {"World", 2 }};
    PrintEx(phrases, [](auto& keyVal){ return keyVal.first;});
    PrintEx(phrases, &std::map<std::string, int>::value_type::second);
}

Play with code @Compiler Explorer.

Abstract

 

Projections are useful “transformers” which you can apply on every vary ingredient earlier than it’s despatched to an algorithm. When you study the fundamentals, you should use them to jot down shorter and extra compact code.

Again to you

  • Have you ever tried Projections?
  • Do you utilize ranges in your tasks?

Be part of the dialogue under.

[ad_2]

Leave a Reply

Your email address will not be published. Required fields are marked *