The Concurrency Runtime and Visual C++ 2010: The decltype Type Specifier

Welcome back! Last time, we looked at how to use the auto keyword in Visual C++ 2010 to implicitly declare local variables based on their initialization (see The Concurrency Runtime and Visual C++ 2010: Automatic Type Deduction). This week we’ll look at yet another great Visual C++ 2010 language feature that you can use when you write code that uses the Concurrency Runtime: decltype.

Imagine that you are writing reusable dataflow components that use the Asynchronous Agents Library. One of these components might be a message-block type that takes two addends and returns their sum. You can create a function, let’s call it make_dataflow_adder, that creates a Concurrency::transformer object. This transformer object takes as input the addends (as a std::tuple object) and produces their sum as output:

    1: template<typename T1, typename T2>
    2: transformer<tuple<T1, T2>, UNKNOWNtype>* make_dataflow_adder()
    3: {
    4:    return new transformer<tuple<T1, T2>, UNKNOWNtype>(
    5:       [](tuple<T1, T2> addends) { return get<0>(addends) + get<1>(addends); }
    6:    );
    7: }

In this example, UNKNOWNtype remains as a placeholder. What should this type be, T1, T2, or something else? operator+ for objects of type T1 and T2 does not always produce a result of type T1 or T2. For instance, the following example shows a case where operator+ for objects of type A and B produces a result of type int:

    1: struct A {   
    2:    int Value;
    3: };
    4:  
    5: struct B { 
    6:    int Value;
    7: };
    8:  
    9: int operator+(const A& a, const B& b)
   10: {
   11:    return a.Value + b.Value;
   12: }

One way to resolve UNKNOWNtype might be to add a third template argument to the make_dataflow_adder function, say TResult, and force the user to provide the result type. Aside from being somewhat of an inconvenience, what if your user is extending your library and does not know what type to provide?

Visual C++ 2010 provides the decltype keyword as a great way to solve this. Simply put, the decltype keyword provides the type of the provided expression. When you use the decltype keyword to rewrite the make_dataflow_adder function, you eliminate the need for a third template type parameter and create code that is more usable:

    1: template<typename T1, typename T2>
    2: transformer<tuple<T1, T2>, decltype(T1() + T2())>* make_dataflow_adder()
    3: {
    4:    return new transformer<tuple<T1, T2>, decltype(T1() + T2())>(
    5:       [](tuple<T1, T2> addends) { return get<0>(addends) + get<1>(addends); }
    6:    );
    7: }

The following shows a complete example that uses the revised make_dataflow_adder function:

    1: // dataflow-adder.cpp
    2: // compile with: /EHsc
    3: #include <agents.h>
    4: #include <complex>
    5: #include <tuple>
    6: #include <iostream>
    7: #include <memory>
    8:  
    9: using namespace Concurrency;
   10: using namespace std;
   11:  
   12: // Utility function that creates a unique_ptr object from the 
   13: // specified native pointer.
   14: template <typename T>
   15: unique_ptr<T> make_unique_ptr(T* ptr)
   16: {
   17:    return unique_ptr<T>(ptr);
   18: }
   19:  
   20: // Creates a transformer object that takes as input two addends (as a tuple)
   21: // and produces their sum as output.
   22: template<typename T1, typename T2>
   23: transformer<tuple<T1, T2>, decltype(T1() + T2())>* make_dataflow_adder()
   24: {
   25:    return new transformer<tuple<T1, T2>, decltype(T1() + T2())>(
   26:       [](tuple<T1, T2> addends) { return get<0>(addends) + get<1>(addends); }
   27:    );
   28: }
   29:  
   30: // An example structure that wraps an int value.
   31: struct A
   32: { 
   33:    A() : Value(0) {}
   34:    A(int value) : Value(value) {}
   35:    int Value;
   36: };
   37:  
   38: // An example structure that wraps an int value.
   39: struct B
   40: { 
   41:    B() : Value(0) {}
   42:    B(int value) : Value(value) {}
   43:    int Value;
   44: };
   45:  
   46: // Sums an A object and a B object to produce an int result.
   47: int operator+(const A& a, const B& b)
   48: {
   49:    return a.Value + b.Value;
   50: }
   51:  
   52: int wmain()
   53: {
   54:    // Use the make_dataflow_adder function to create several transformer 
   55:    // objects that add different kinds of types.
   56:  
   57:    auto t1 = make_unique_ptr(make_dataflow_adder<int, int>());
   58:    auto t2 = make_unique_ptr(make_dataflow_adder<wstring, wstring>());
   59:    auto t3 = make_unique_ptr(make_dataflow_adder<complex<double>, complex<double>>());
   60:    auto t4 = make_unique_ptr(make_dataflow_adder<A, B>());
   61:  
   62:    // Send to and receive from the transformer objects to illustrate
   63:    // their behaviors.
   64:  
   65:    // int + int
   66:    wcout << L"4 + 42 = ";
   67:    send(*t1, make_tuple(4, 42));
   68:    wcout << receive(*t1) << endl;
   69:  
   70:    // wstring + wstring
   71:    wcout << L"\"Hello, \" + \"World!\" = ";
   72:    send(*t2, make_tuple(wstring(L"Hello, "), wstring(L"World!")));
   73:    wcout << L'\"' << receive(*t2) << L'\"' << endl;
   74:  
   75:    // complex<double> + complex<double>
   76:    wcout << L"(3.0,4.0) + (2.0,-1.0) = ";
   77:    send(*t3, make_tuple(complex<double>(3.0, 4.0), complex<double>(2.0, -1.0)));
   78:    wcout << receive(*t3) << endl;
   79:  
   80:    // A + B
   81:    wcout << L"A(50) + B(25) = ";
   82:    send(*t4, make_tuple(A(50), B(25)));
   83:    wcout << receive(*t4) << endl;
   84: }

This example produces the following output:

4 + 42 = 46

"Hello, " + "World!" = "Hello, World!"

(3.0,4.0) + (2.0,-1.0) = (5,3)

A(50) + B(25) = 75

Although the decltype keyword was created primarily for library developers, anyone who is interested in writing generic, reusable code can make use of this feature.

For more information about decltype, including how to use decltype and auto to declare a function that has a late-specified return type, see decltype Type Specifier and Stephan’s post Lambdas, auto, and static_assert: C++0x Features in VC10, Part 1.

Next time we’ll look at rvalue references, a Visual C++ language feature that can help you improve the performance of your applications that use Concurrency Runtime.