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.

Comments (5)

  1. MZ says:

    decltype(T1() + T2()), clean as it may look, is flawed, since it only works if T1 and T2 have public default constructors, which is a strong assumption. Instead, one might want to use a more universal, yet obscenely ugly piece of code:

    decltype(*(T1 *)0 +*(T2*)0)

    it makes no assumptions about T1 and T2 except that they can be added with a '+'.

    Of course, some macro can be used to avoid rewriting this ugly code many times:

    #define OpType(T1, T2, Operator) decltype(*(T1 *)0 Operator *(T2 *)0)

  2. Hi, unfortunately it will be me again, who will be the "bad guy" but let me assure you that I mean/have no bad intentions, the only thing that I want is that for certain things to be done correctly.

    First question: Why do you write about features that has been already extensively explained by people who work for the same company you work for? If I want to read about auto: blogs.msdn.com/…/lambdas-auto-and-static-assert-c-0x-features-in-vc10-part-1.aspx, if I want to read about rvalues (by the way do us a favour and don't write about them for they are already comprehensively described in many places by many people): blogs.msdn.com/…/lambdas-auto-and-static-assert-c-0x-features-in-vc10-part-1.aspx.

    Also don't write about lambda nor static_assert (see first link I've provided).

    My advice for you: If you really feel that you have to write about something first check if subject you think of has been already explained (at least within your company resources), and if the answer is yes, then don't do it – what is the point of repeating something someone already did?

    But there are numbers of topics from C++11 that hasn't been "touched" by anyone (as far as I'm concerned) – so pick one and write about them.

    But my feeling is that all this will be ignored and you will write about rvalues anyway. And after that you'll write about lambdas. And after you someone else from MS will start it all over again until new features will be implemented in VS compiler and then they will be "discussed" repetitively by folks who instead of writing something original they write about things that already had been written tens of times if not more.

    Regards

  3. Thomas Petchel - MSFT says:

    MZ, thanks for sharing this. I showed the "clean" version just to demonstrate the concept. I'm glad that you pointed out the case where T1 or T2 doesn't have a default constructor.

    I don't think that you can apply it to this example, but you can also use so-called trailing return type syntax when you have a template function that takes parameters:

    template <typename T, typename U>

    auto add((T&& t, U&& u) -> decltype(forward<T>(t) + forward<U>(u))

    {

      return forward<T>(t) + forward<U>(u);

    }

  4. Thomas Petchel - MSFT says:

    Knowing, thanks for your feedback. I wish to apologize for this article not being helpful to you.

    I agree that there are a lot of great resources out there about the new language features in Visual C++. My intention was to illustrate how to use these features specifically with the PPL and Agents libraries. This article is actually 1 in a series of 5 (and yes, I already wrote about rvalue references and lambdas :D). I also linked to the pages that you cited, just to make sure that folks can read the whole story.

    It also sounds like there are areas of Visual C++ that you'd like to see covered in greater detail. We'd love to hear about what you're working on and what you'd like to read more about!

  5. Thomas thanks for your reply. Yes, you're right when you say that there are areas of (and I swear this just came to me now), why do you call it Visual C++? As far as I'm concerned the language you writing about is called C++ (and some suffix), so could you please call it as its correct name is? Unless you're really writing about some language called Visual C++ then also could you please make it clear from the very beginning? (I do understand that in that moment I sound like … but I'm not I just want things to be called by their correct name)

    Ok let's go back to the main point. Yes, there are areas of new ** C++ ** and I believe if I call it C++11 no one will be offended, that I would really like to read/know more about and first thing what comes to my mind is multithreading (using only std facilities).

    What I would also like to see (this is somewhat included) is that I would like to see examples which are written using good programming practices, and from which less advanced users could really learn something valuable.

    But yours isn't. Let me take just few:

    1. First your main is incorrect – doesn't have return statement. (Paste and copy problems? (I don't believe in that but…), well it is up to you to make sure that your articles (job) has certain (preferably high), standard).

    2. Using directives are not a good practice (in general), would you agree on that? Especially bringing to the scope everything from the std is seen as a BIG NO NO.

    3. Having any struct which isn't POD (like your A or B) without clearly defined public and private scopes is a bad programming practice (in general), would you agree on that?

    And yes, I am aware that you can have tens if not hundreds of clever explanations (better readability, copy and paste problems etc. etc.) but, and I'll say it again, it is you who should take every possible care to ensure that you deliver high quality product.

    And if you do not warn in the beginning of your article (or anywhere really) that your examples are in that way because **this or that** how "the less advanced user" should now that the style you code is to be avoid? Has he been warned?

    I also would like to see articles like yours with clearly specified:

    1. Group of users (beginners, advanced etc.) they are aimed at.

    2. Type to which your article could be classified (tutorial, short demo, briefing etc.)

    And Thomas… for the love of God could you please try to compile your examples before you post it? (Just to make sure that your work is of high quality not some …). Your example (the one you've posted in response to MZ) is, excuse the pun "an example" of poor quality and lack of professional approach to a job.

    So summarizing:

    0. Could you please answer if your articles about rvalues and lambdas are bringing anything new to the subject than those articles available for long time on MSDN?

    1. Do you feel up to a scratch and could you write series of articles about multithreading in C++11 using **only** std facilities?

    2. Could you please classify your articles – what are they and for whom they are? Are they tutorials? Are they for beginners?

    3. Could you please take care and use good programming practices in your future examples? And if you (for some reason) prefer not to, could you please warn the less experienced users about this fact?

    Regards

    Artur Czajkowski.