Channels ▼
RSS

C/C++

LINQ-like List Manipulation in C++


When developers make the move from C# to C++, they definitely miss Language-Integrated Query (LINQ) features. Cpplinq is an excellent open-source project that uses C++11 features to provide extensible LINQ-like list manipulation to C++. It works with the latest versions of the popular C++ compilers that implement the required C++ 11 features: Visual Studio, g++, clang++ and MinGW. In this article, I'll provide a few examples of the usage of Cpplinq for those of you who have incorporated LINQ in your practices and don't want to change that way of coding when working with C++.

C++11 Features and Extensibility

While there are many options to simulate LINQ in C++, but Cpplinq is the one I like best because it uses C++11 features, and thus provides a modern C++ implementation with a special focus on good performance and reduced overhead. Cpplinq requires C++ compilers to support these C++11 features: auto, decltype, lambdas, rvalue-reft, and static_assert.

The main difference you will notice between LINQ and Cpplinq is that the latter uses the >> operator instead of the . operator. The main reason for this decision is that it is possible to overload the >> operator, so its usage allows you to extend Cpplinq. Obviously, it takes some time to get used to using >> instead of . when writing LINQ-style queries in C++. You will definitely use the backspace key dozens of times before getting the hang of it.

Cpplinq includes support for most query operators defined in LINQ (.NET Framework). In Cpplinq, the operator names are lowercase and, in many cases, use the underscore character to separate words. For example, Cpplinq where is equivalent to LINQ Where, and Cpplinq element_at_or_default is equivalent to LINQ ElementAtOrDefault. The following list includes the LINQ operators that are not included in Cpplinq. As you can see, you will miss a few operators.

  • Aggregate operator:
    • LongCount.
  • Conversion operators:
    • AsEnumerable.
    • ToArray.
    • OfType.
    • Cast.
  • Element operators:
    • First.
    • Last.
    • Single.
    • SingleOrDefault.
    • ElementAt.
    • DefaultIfEmpty.
  • Join operator:
    • GroupJoin.

Working with LINQ-style Queries

The following lines show a simple example of a struct that represents a game with an ID, a name, a counter for the number of times the game is played, and a high score value. I've included the necessary includes in the lines. Notice that Cpplinq requires only a single include line: #include "cpplinq.hpp"

#include "stdafx.h"
#include "cpplinq.hpp"
#include <string.h>

struct game
{
	std::size_t id;
	std::string name;
	unsigned int played_count;
	unsigned int high_score;
};

The following lines define a games array with eight elements of the previously introduced struct:

game games[] =
{
	{ 1001, "Crazy Kong", 30, 50000 },
	{ 1002, "Spider Woman III", 15, 25300 },
	{ 1003, "Bubble Crazyness", 20, 32000 },
	{ 1004, "Infinity Mania", 50, 89000 },
	{ 1005, "Highest Score Game", 300, 156000 },
	{ 1006, "Second Highest Score Game", 850, 156000 },
	{ 1007, "Never Played", 0, 0 },
	{ 1008, "Played Just Once - No Score", 1, 0 }
};

For demonstration, I will use the games array in some typical scenarios where LINQ simplifies code. The following lines use the cpplinq namespace to perform a LINQ-style array manipulation with many Cpplinq operators and C++11 lambda expressions.

using namespace cpplinq;
auto highest_score_game_name = from_array(games)
	// Consider only the games that were played at least once
	>> where([](game const & g) { return g.played_count > 0; })
	>> orderby_descending([](game const & g){return g.high_score; })
	>> thenby_ascending([](game const & g){return g.name; })
	>> select([](game const & g){return g.name; })
	>> first_or_default();

First, the from_array range source creates a range from an array and serves as a kick-off for the query. As happens in LINQ, once you have the source, you can start chaining the different operators that you want to apply to build the query.

The query filters the games that were played at least once by using the where restriction operator (equivalent to a LINQ Where) with a C++11 lambda expression that returns a bool value. One of the key differences between Cpplinq and other libraries that try to provide LINQ-style queries to C++ is that Cpplinq doesn't fake lamdbas. Thus, with Cpplinq, you can leverage your existing knowledge of C++11 lambda expression and use them with the different operators:

>> where([](game const & g) { return g.played_count > 0; })

Next, the query chains two ordering operators: orderby_descending and thenby_ascending. The two C++11 lambda expressions return the game field that must be used for each sort process. First, the query orders by the high score (descending), and then by the game's name (ascending).

>> orderby_descending([](game const & g){return g.high_score; })
>> thenby_ascending([](game const & g){return g.name; })

Then, the query uses the select project operator (equivalent to a LINQ Select) with a C++11 lambda expression to retrieve just the game's name.

>> select([](game const & g){return g.name; })

Finally, the query uses the first_or_default element operator (equivalent to a LINQ FirstOrDefault) to retrieve the first game's name or the default value.

>> first_or_default();

In this case, the value for highest_score_game_name is "Highest Score Game." Two games that were played have the same value for high_score, 156000, but the ordering operators make a final ascending sort by the game's name that puts "Highest Score Game" in the first position.

{ 1005, "Highest Score Game", 300, 156000 }
{ 1006, "Second Highest Score Game", 850, 156000 }

If you have some experience with LINQ, you can see that it is really easy to start working with Cpplinq. In case you haven't worked with C++11 lambdas before, it shouldn't take too much time to grasp their syntax and use them in Cpplinq with the examples included in this article.

A typical scenario for a LINQ-style query is to generate a filtered list as a result of a query. The following lines use the cpplinq namespace to perform a LINQ-style array manipulation with many Cpplinq operators and lambda expression borrowed from the previous example. However, in this case, the query uses the to_list conversion operator (equivalent to a LINQ ToList) to generate a std::list<game,std::allocator<game>> with the seven game instances that include a played_count value higher than 0, ordered by their high_score value (descending) and then by their name (ascending).

using namespace cpplinq;
auto highest_scores_for_played_games = from_array(games)
	// Consider only the games that were played at least once
	>> where([](game const & g) { return g.played_count > 0; })
	>> orderby_descending([](game const & g){return g.high_score; })
	>> thenby_ascending([](game const & g){return g.name; })
	>> to_list();

When working with Cpplinq, you may find it convenient to use the C++11 auto keyword for the variable that defines the results of the chained query operators. In the previous lines, the auto keyword makes it easier to read the first line. If you don't use the auto keyword, the first lines would read as follows:

using namespace cpplinq;
std::list<game,std::allocator<game>> highest_scores_for_played_games = from_array(games)

If you use the to_vector conversion operator instead of to_list, the query will return a std::vector<game,std::allocator<game>>.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video