The Concurrency Runtime and Visual C++ 2010: Automatic Type Deduction

Last time, we looked at how to use lambda expressions in Visual C++ 2010 to make the Concurrency Runtime even easier to use (see The Concurrency Runtime and Visual C++ 2010: Lambda Expressions). This week we’ll take a quick look at the auto keyword, which is another language feature that can enhance your productivity.

We’ll first look at the verbose way to declare a template type that is provided by the Asynchronous Agents Library. We’ll then use template argument deduction to refine this declaration. Finally, we’ll use the auto keyword to make the declaration super-succinct.

Consider the following example that uses the Concurrency::make_join function to create a Concurrency::multitype_join object. The multitype_join class represents a message block type that combines messages from multiple source buffers.

    1: // explicit-creation.cpp
    2: // compile with: /EHsc
    3: #include <agents.h>
    4: #include <vector>
    5:  
    6: int wmain()
    7: {
    8:    using namespace Concurrency;
    9:    using std::vector;
   10:    using std::tuple;
   11:  
   12:    single_assignment<bool>       b1;
   13:    unbounded_buffer<double>      b2;
   14:    overwrite_buffer<vector<int>> b3;
   15:  
   16:    multitype_join<tuple<
   17:       single_assignment<bool>*, 
   18:       unbounded_buffer<double>*,
   19:       overwrite_buffer<vector<int>>*>> j = make_join<
   20:                                           single_assignment<bool>*, 
   21:                                           unbounded_buffer<double>*,
   22:                                           overwrite_buffer<vector<int>>*>(&b1,
   23:                                                                           &b2,
   24:                                                                           &b3);
   25: }

Although templates are a powerful way to enable you to make use of reusable libraries, the declaration of j (lines 16-24) was rather tedious to write. It’s also more difficult to visualize at a glance what’s going on. Thanks to template argument deduction in the Visual C++ compiler, we can rewrite this example to eliminate the template argument specification on the right-hand side of the assignment expression:

    1: multitype_join<tuple<
    2:    single_assignment<bool>*, 
    3:    unbounded_buffer<double>*,
    4:    overwrite_buffer<vector<int>>*>> j = make_join(&b1, &b2, &b3);

Although this revised example eliminates the need to specify the template types on both sides of the assignment expression, it’s still somewhat tedious to write. If you’re familiar with implicit typing on other languages (such as var in C#), it seems reasonable that the Visual C++ compiler can also infer the types of local variables based on their initial value. In fact, Visual C++ 2010 supports the use of the auto keyword to support implicit typing.

Sidebar: Although it was rarely used, you may have already seen auto used in C++ code. Before Visual C++ 2010, auto was used as a storage-class specifier. – End Sidebar

Here’s a final example that revises the previous one to use the auto keyword to entirely eliminate the need to provide type arguments in the assignment expression:

    1: auto j = make_join(&b1, &b2, &b3);

There you have it! The auto keyword is a convenient way to declare instances of container types and iterators in the Standard Template Library and the Concurrency Runtime. It can also help you use functions that take a type that is either difficult or impossible to express explicitly. For example, the Concurrency::structured_task_group class requires you to manage the lifetime of the work function for each task. However, if you use a lambda expression to define this work function, you cannot know its type because lambda functions have anonymous type. Therefore, you can use the auto keyword when you declare the lambda expressions, like the following (you can find the full example here):

    1: // Use the make_task function to define several tasks.
    2: auto task1 = make_task([] { /*TODO: Define the task body.*/ });
    3: auto task2 = make_task([] { /*TODO: Define the task body.*/ });
    4: auto task3 = make_task([] { /*TODO: Define the task body.*/ });
    5:  
    6: // Create a structured task group and run the tasks concurrently.
    7:  
    8: structured_task_group tasks;
    9:  
   10: tasks.run(task1);
   11: tasks.run(task2);
   12: tasks.run_and_wait(task3);

Although auto is a great time saver, overuse can cause your code to become hard to read. I try to limit its use to when I work with complex template types or when I need to create a lambda expression variable. For more information and examples of how to use the auto keyword, see auto Keyword (Type Deduction) and Stephan’s post Lambdas, auto, and static_assert: C++0x Features in VC10, Part 1.

 

Next time we’ll look at another language feature in Visual C++ 2010 that works great with the auto keyword.