RSS

The anatomy of ‘const T’?

The title of the post may raise eyebrows, but it is something that stumped me long time back. I did read up a couple of good articles/books this issue and I understood. Well, at least that’s what I thought until I re-stumped once again very recently, while glancing through some code listing on the internet. In this post on ‘const T’, I won’t be too off, if I admit that my understanding of the concepts on the first instance was indeed ‘volatile’. So, let’s start at the very beginning. What does the declaration below mean?

int *(*fp[10])(int, char);

Does it mean ‘fp’ is a ‘pointer to an array of 10 pointers to a function which takes an integer and a char and returns a pointer to an int’? Or, does it mean ‘fp is an array of 10 pointers to a function which takes and integer and a char and returns a pointer to an int’? Or, does it mean something else? Is it even well-formed declaration?

Such declarations in C and C++ are best interpreted using the Right-Left rule. Broadly speaking, the process is as follows:

a) Start at the innermost parenthesis:
Here we say                                               ‘fp is a

b) Proceed right and we hit operator []:
Here we say                                               ‘fp is an array of 10 elements’

c) Now immediately turn left and keep looking at previously unconsidered tokens. Now we hit operator *: Here we say                                                       ‘fp is an array of 10 elements of pointer to

d) Now immediately turn right and keep looking at previously unconsidered tokens. Now we hit operator ):
Here we say                                               ‘fp is an array of 10 elements of pointer to function where each function

e) Now immediately turn left and keep looking at previously unconsidered tokens. Now we hit operator ). This we already considered as identifying a function. Keep going left. Now we hit operator *
Here we say                                               ‘fp is an array of 10 elements of pointer to function where each function returns a pointer to an int

f) Now immediately turn left and keep looking at previously unconsidered tokens. Now we hit operator (.
Here we say                                               ‘fp is an array of 10 elements of pointer to function where each function returns a pointer to an int and takes an integer and a char as its arguments’

Therefore, the above declares ‘fp ss an array of 10 elements of pointer to function where each function returns a pointer to an int and takes an integer and a char as its arguments’. Such a function is perfectly well-formed in C++.

int *f(int, char) { return nullptr; }

int main() {
int *(*fp[10])(int, char) = {f};
}

Now, let us say that you are given an assignment to declare a variable of type ‘const int’. Invariably, most of the programmers would write like so:

const int x = 0;

Applying the ‘right left rule’, we would read the type of ‘x’ as ‘integer constant’. Not very descriptive right. What about the below declaration when interpreted using the ‘right left rule’?

int const x = 0;

Now the declaration of ‘x’ is more natural and intuititve. Clearly, ‘x’ is a ‘constant integer’. Note that both the declarations are semantically well-formed and mean one and the same. It’s just that second declaration is so much more intuitive and natural to read. In general, we can extend this and say that ‘T const’ is the same as thing as ‘const T’. But, there are certain places where the ‘T const’ and ‘const T’ assumes much more significance than just a simple style issue. Consider the following code snippet

int main() {
    typedef int * PINT;
    typedef const PINT CPINT;

    int x;
    PINT p1 = &x;
    CPINT p2 = p1;
    p2++;
}

The question now of course, is about the validity of the expression ‘p2++’? Is it well-formed or ill-formed? Here’s what most developers do: Substitute PINT as ‘int *’ in the typedef for CPINT, which gives them the following declaration

typedef const int* CPINT;

This makes CPINT as an alias for ‘const int *’. Now, the declaration for ‘p2′ is interpreted as ‘pointer to a constant integer’.

const int* p2 = p1;

This means that ‘p2′ can be modified but the contents of ‘p2′ can’t. But alas, this analysis is faulty. The expression ‘p2++’ is ill-formed. But why? This is because ‘p2′ is effectively declared as ‘constant pointer to integer’ and not as ‘pointer to constant integer’.  Surprised? Confused. At least I was. That’s what I meant by ‘stumbled’ at the beginning of this post. Earlier in the post I mentioned how ‘T const’ is more descriptive than ‘const T’. It clearly states that ‘T is const’ and is immutable. Let’s rewrite the typefefs again in the form ‘T const’

int main() {
    typedef int * PINT;
    typedef PINT const CPINT; // note the respositioned 'const'

    int x;
    PINT p1 = &x;
    CPINT p2 = p1;
    p2++;
}

Now, once again as before CPINT is a typedef for ‘const pointer to PINT’ and PINT is a ‘pointer to int’. Therefore, CPINT is a typedef for ‘const pointer to int’. Now, it is easy to see that the type of ‘p2′ is ‘constant pointer to an integer’. The pointer ‘p2′ can’t be modified but it’s contents can be. Therefore the expression ‘p2++’ on Line 8 is ill-formed.

A nice article which explains this subtle issue and the other associated details much clearly can be found here

 
Leave a comment

Posted by on November 23, 2012 in C++

 

Tags: , , ,

C++11 ‘auto’ keyword-Part 2

In part 1, we briefly looked at some of the basics of the C++11 keyword ‘auto’. In this concluding article we look at some more well and ill-formed uses of the ‘auto’ keyword with references to the C++11 Standard.

Let’s illustrate some of the ill-formed and well-formed uses of the C++11 ‘auto’ type specifier with the help of a sample program.

#include <initializer_list>
template<auto x = 2> class TClass {    // ill-formed: $7.1.6.4/5
};
void f(auto x = 2) {                   // ill-formed: $7.1.6.4/5
}
class MyClass{
public:
    static auto x = 2;                 // well-formed: $7.1.6.4/4
};
auto MyClass::x;                       // ill-formed: $7.1.6.4/4
int main() {
    char a[5] = "ABCD";
    auto ae1[5] = "ABCD";              // ill-formed: $8.3.4
    auto ae2 = "ABCD";                 // ae2 deduced as char const *
    char b[4] = {'A', 'B', 'C', 'D'};
    auto be[4] = {'A', 'B', 'C', 'D'}; // ill-formed: $8.3.4
    auto ce = {'A', 'B', 'C', 'D'};    // well-formed: $7.1.6.4/6
    auto int x = 0;                    // ill-formed: $7.1.6.2
    auto y;                            // ill-formed: $7.1.6.4/3- each of which shall have a
                                       // non empty initializer
    std::vector<int> v {1, 2, 3};
    for(auto val: v) {                 // well-formed: $7.1.6.4/4
        std::cout << val << std::endl;
    }
    if(auto x = !v.empty()) {          // well-formed: $7.1.6.4/4
        std::cout << "Vector is not empty" << std::endl;
    }
}

$7.1.6.4/3:
Otherwise, the type of the variable is deduced from its initializer. The name of the variable being declared shall not appear in the initializer expression. This use of auto is allowed when declaring variables in a block (6.3), in namespace scope (3.3.6), and in a for-init-statement (6.5.3). auto shall appear as one of the decl-specifiers in the decl-specifier-seq and the decl-specifier-seq shall be followed by one or more initdeclarators, each of which shall have a non-empty initializer.

$7.1.6.4/5:
A program that uses auto in a context not explicitly allowed in this section is ill-formed.

  • As is clear from the above quotes from the Standard, usage of the C++11 keyword ‘auto’ is ill-formed on Line 2 and Line 4$7.1.6.4/4:
    The auto type-specifier can also be used in declaring a variable in the condition of a selection statement (6.4) or an iteration statement (6.5), in the type-specifier-seq in the new-type-id or type-id of a new-expression (5.3.4), in a for-range-declaration, and in declaring a static data member with a brace-or-equal-initializer that appears within the member-specification of a class definition (9.4.2).
  • In Light of the above quote, Line 8 is well-formed, since the initializer appears within the member-specification of a class definition. However, the usage of the ‘auto’ keyword on Line 10 is ill-formed as it is outside of the class definition.
  • Line 13 and 16 are ill-formed. This is in light of the following paragraph from the Standard$8.3.4/1:
    In a declaration T D where D has the form D1 [ constant-expressionopt] attribute-specifier-seqopt and the type of the identifier in the declaration T D1 is “derived-declarator-type-list T”, then the type of the identifier of D is an array type; if the type of the identifier of D contains the auto type-specifier, the program is ill-formed.
  • Line 17 is well-formed. This is governed by the paragraph$7.1.6.4/6:
    [...]if the initializer is a braced-init-list (8.5.4), with std::initializer_list<U>[...]
  • Line 18 is ill-formed. It is easy to guess why. At most one type specifier is allowed by the language grammar.
    $7.1.6/2:
    As a general rule, at most one type-specifier is allowed in the complete decl-specifier-seq of a declaration or in a type-specifier-seq or trailing-type-specifier-seq.
  • Line 19 is also ill-formed and once again it is easy to see why.$7.1.6.4/4:
    [...]the decl-specifier-seq shall be followed by one or more initdeclarators, each of which shall have a non-empty initializer.[...]‘

With this, we conclude the article exploring the C++11 keyword ‘auto’. I will be happy to hear from you and receive your comments and feedback.

 
Leave a comment

Posted by on November 20, 2012 in C++

 

Tags: , , , , ,

Powershell quickies

Powershell tips:

  • To search for the whole word “DVD” without regards to case recursively in all files matching a given criteria in a specified folder
     get-childitem V:\. -recurse -include *.cpp, *.h | Select-String -pattern "\bDVD\b"
     
  • To find the unique files containing the word “DVD” without regards to case recursively in all files matching a given criteria in a specified folder
     get-childitem . -recurse -include *.cpp, *.h | Select-String "DVD" | Select-Object -Unique Path
     
  • To find the number of unique files containing the word “DVD” without regards to case recursively in all files matching a given criteria in a specified folder
    • Approach 1
       (get-childitem . -recurse -include *.cpp, *.h | Select-String "DVD" | Select-Object -Unique Path).count
       
    • Approach 2
       >$a= get-childitem . -recurse -include *.cpp, *.h | Select-String -pattern "DVD" | Select-Object –Unique
      >$a.GetType()                 # The output shows that is an array type
      >Get-Member -inputobject $a   # List all members of System.Array
      >$a.count
      
  • To find the context containing the word “DVD” without regards to case recursively in all files matching a given criteria in a specified folder
    get-childitem V:\. -recurse -include *.cpp, *.h | Select-String -context 2,2 -pattern "DVD"
    
  • Assume a text file (a.txt) with the following contents
    dvd
    dvd VBD
    dvdVBD
    DVD is good
    DVD is VBDTo find out lines in the file a.txt, which contain the word “DVD” but not “VBD”, the powershell command is:
    get-childitem . -recurse a.cpp | Select-String -pattern "DVD" | Select-string -pattern "^(?!.*VBD)"
    

    Or, alternatively

    get-childitem . -recurse a.cpp | Select-String -pattern "DVD" | Select-string -notmatch "VBD"
    
 
Leave a comment

Posted by on November 20, 2012 in Powershell

 

Tags:

C++11 ‘auto’ keyword-Part 1

In this post, and the next I discuss about the C++11 keyword ‘auto’.

A bit of background

The keyword ‘auto’ in C++ 2003 indicates storage duration.Storage duration is the property of an object that defines the minimum potential lifetime of the storage containing the object.

C++03, $3.7.2/1:

Local objects explicitly declared auto or register or not explicitly declared static or extern have automatic storage duration. The storage for these objects lasts until the block in which they are created exits.

In C++11, the keyword auto is no longer used to indicate automatic storage duration. As Bjarne says here,

The old meaning of auto (“this is a local variable”) is now illegal. Several committee members trawled through millions of lines of code finding only a handful of uses — and most of those were in test suites or appeared to be bugs.

Before, we delve into the details of the new meaning of the keyword ‘auto’, it is important to note that C++11 continues with the notion of automatic storage duration. It is just that ‘auto’ keyword is no longer used to describe it. In fact, there is no keyword in C++11 to specify automatic storage duration.

C++11, $3.7.3/1:

Block-scope variables explicitly declared register or not explicitly declared static or extern have automatic storage duration. The storage for these entities lasts until the block in which they are created exits.

The present

A Brief Introduction

Until now, the declaration of a variable requires it’s type to be specified explicitly at compile time. This is because C++ is a statically typed language where the type checking it performed at compile time.

 int x = 7;
 

In the above declaration, it is required to specify the type of the variable ‘x’ so that the necessary type checking can be done by the compiler statically.

But is it really necessary to specify the type of the ‘x’ explicitly as ‘int’?

It turns out that compiler can automatically deduce the type of the variable ‘x’ based on the type of the expression used to initialize ‘x’. The expression used to initialize ‘x’ is the integer literal 7. In accordance with $2.14.2/1, the type of the integer literal 7 is ‘int’. Therefore, the compiler deduces the type of ‘x’ as ‘int’. This is where the ‘auto’ keyword comes into picture. In a way we tell the compiler to go and figure out the right type for the variable being declared based on the type of the initializer expression.

 auto x = 7; // x is deduced as 'int' by the compiler.
 

At this point, we can make the following observations:
-The compiler can only deduce the type of a variable declared with the ‘auto’ specifier, only when it has an initializer.
-The automatic deduction of the variable type does not compromise type-safety of the language. In the above example, once type of ‘x’ is deduced as ‘int’, ‘x’ can not be used in a way which violates the rules of the language. As an example, ‘x’ can not be used to point to a string literal.

 auto x = 7;   // x is deduced as 'int' by the compiler
 x = "ABCD";   // cannot convert from char[5] to int
 

Here are some more examples of how ‘auto’ type specifier can be used. Note that in contrast to the above examples declaring variable ‘x’ using the copy initialization syntax, the next example shows the versatility of the auto type specifier for declaring variables using the direct initialization syntax

 auto y(255.0);  // y deduced as ‘double’ by the compiler
 auto z(y);      // z deduced as ‘double’ by the compiler
 

The auto type specifier works with more advanced types as well

 template bool f(T *p1, T *p2) {
     bool bret = false;
     // do whatever
     return bret;
 }
 int main() {
     bool (*p)(int *, int *) = f;
     // C++11 only
     auto handler = f;
 }
 

An obvious question now that comes into mind is to know the type that the compiler deduces for the variable ‘handler’. There are many possibilities. One easy way to figure out the deduced type of ‘handler’ is to simply do something absurd with it. For example

 handler = 2;
 

Now notice the diagnostic that the compiler issues
error C2440: ‘=’ : cannot convert from ‘int’ to ‘bool (__cdecl *)(T *,T *)’ 1>          with 
1>          [ 
1>              T=int 1>          ] 
1>          Conversion from integral type to pointer type requires reinterpret_cast, C-style cast or function-style cast

There you go. The compiler clearly spits out the deduced type of ‘handler’ right on our face.

Another possibility is to use the ‘std::type_info::name’ to print out the internal name of the variable. However, the effectiveness of this approach is implementation dependent ($18.7.1/9)

At this point, the C++11 ‘auto’ keyword appears to have the benefit of encouraging less program verbosity. The programmer now has to do far less typing and can now instead comfortably rely on the implementation to go and figure out the right type. Indeed, the code snippet below shows some more examples of convenience that comes with the use of the ‘auto’ type specifier.

 int main() {
     auto *p1 = new auto(2); // p1 is deduced as int *
     auto p2 = new auto(2);  // and so is p2
     std::vector v {1, 2, 3};
     // no need to use vector::iterator it = v.begin()
     for(auto it = v.begin(); it != v.end(); ++it) {
         std::cout << *it << std::endl;
     }
 }
 

Note that there is no need to specify the exact type of the iterator it. With the use of the ‘auto’ keyword, we let the compiler figure it out for itself. Much less typing and type inference on the part of the programmer!

But is it really programmer convenience beneath the C++11 ‘auto’ keyword or is there something yet more powerful to it. Let’s look at an example

 template T Addit(T const &t, U const &u) {
     T temp = t;
     temp = temp + u;
     return temp;
 }

 int main()  {
     std::cout << Addit(2, 3) << std::endl;   // 5
     std::cout << Addit(2.2, 3) << std::endl; // 5.2;
     std::cout << Addit(3, 2.2) << std::endl; // 5;
 }
 

In the function template ‘Addit’, we return the result of adding variables of type T and U. The return value is of type ‘T’. As shown in the comments, the result of 2.2 + 3 is rightly 5.2 but the result of 3 + 2.2 is 5. This is because in the call to the function template Addit(3, 2,2), T is of type ‘int’ and U is of type ‘double’. The return value of this function call is of type ‘int’. Therefore, the statement ‘temp = temp + u’, drops precision and converts the double value 5.2 into integer value 5 which is eventually returned and printed.

Interchanging ‘T’ and ‘U’ is not a solution because in that case ‘Addit(2.2, 3)’ returns 5 instead of 5.2. The core reason of this issue is that C++ does not have a notion of determining the wider of any two given types ‘T’ and ‘U’. So, what is the solution?

One possible solution is to have the function template accept another template parameter ‘W’. At the site of instantiation, the corresponding template argument for ‘W’ is explicitly specified, since it can not be deduced by the compiler from the function call arguments.

 template W Addit(T const &t, U const &u) {
     W temp = t;
     temp = temp + u;
     return temp;
 }
 int main() {
     std::cout << Addit(2, 3) << std::endl;   // 5
     std::cout << Addit(2.2, 3) << std::endl; // 5.2;
     std::cout << Addit(3, 2.2) << std::endl; // 5;
 }
 

Note, how, each of the instantiations specifies the template argument for the template parameter ‘W’ explicitly as ‘double’. So, now that we have solved the problem, but are it’s pros and cons?

The programmer now explicitly needs to specify the return type of ‘Addit’ during instantiation. This gives the programmer much finer control over the behavior of ‘Addit’. The drawback is that programmer now needs to decipher the appropriate return type mandatorily.

C++11 allows a very elegant solution when the ‘auto’ keyword is used in conjunction with the ‘decltype’ keyword (which is new in C++11) and the ‘trailing function return type’ feature (which is new in C++11)

 template<typename T, typename U> auto Addit(T const &t, U const &u) -> decltype(t + u) {
     auto temp = t + u;
     return temp;
 }

 int main() {
     std::cout << Addit(2, 3) << std::endl;   // 5
     std::cout << Addit(2.2, 3) << std::endl; // 5.2;
     std::cout << Addit(3, 2.2) << std::endl; // 5;
 }
 

Note that in the function template above, we let the compiler figure the type of the local variable ‘temp’ based on the initializer expression ‘t + u’. In our context, if either of them is a double, the result of the expression is the wider of the two which is ‘double’. So, in general, instead of the programmer specifying the return type, we let the compiler figure it out based on the return type of the operator + that is selected by overload resolution. Note, that the return type of the function template ‘Addit’ is now specified as ‘auto’ and the exact return type is specified by the type specified by the expression ‘decltype(t + u)’.

The above example is illustrative of the fact that in some situations it is just impossible or too laborious for the programmer to determine a type. It is in these circumstances that the power of the ‘auto’ keyword is unleashed to it’s fullest, much beyond as a mere convenience tool for the programmer.

Under the hood

Before, we see some more examples of ‘auto’, it is important to understand how the ‘auto’ keyword really works. Since the ‘auto’ keyword is involved with type deduction, it is not hard to guess that the rules governing ‘auto’ type deduction are based on template argument deduction rules. $7.1.6.4 specifies the details of the C++11 ‘auto’ keyword.

$7.1.6.4/6:

Once the type of a declarator-id has been determined according to 8.3, the type of the declared variable using the declarator-id is determined from the type of its initializer using the rules for template argument deduction. Let T be the type that has been determined for a variable identifier d. Obtain P from T by replacing the occurrences of auto with either a new invented type template parameter U or, if the initializer is a braced-init-list (8.5.4), with std::initializer_list<U>. The type deduced for the variable d is then the deduced A determined using the rules of template argument deduction from a function call (14.8.2.1), where P is a function template parameter type and the initializer for d is the corresponding argument. If the deduction fails, the declaration is ill-formed.

Here are some of the important statements from $14.8.2.1 that are reproduced here for easy reference.

$14.8.2.1/1:
Template argument deduction is done by comparing each function template parameter type (call it P) with the type of the corresponding argument of the call (call it A) as described below.

$14.8.2.1/2:
If P is not a reference type:
— If A is an array type, the pointer type produced by the array-to-pointer standard conversion (4.2) is used in place of A for type deduction; otherwise,
— If A is a function type, the pointer type produced by the function-to-pointer standard conversion (4.3) is used in place of A for type deduction; otherwise,
— If A is a cv-qualified type, the top level cv-qualifiers of A’s type are ignored for type deduction.

$14.8.2.1/3:
If P is a cv-qualified type, the top level cv-qualifiers of P’s type are ignored for type deduction. If P is a reference type, the type referred to by P is used for type deduction. If P is an rvalue reference to a cvunqualified template parameter and the argument is an lvalue, the type “lvalue reference to A” is used in place of A for type deduction.

Let us now see how these rules kick in to the type deduction process with the ‘auto’ keyword with some examples.

Given the declarations

 int x, &lvrx = x, *px = &x;
 int const ci = 2, &lvrci = ci, *pci = &ci;
 

Consider the declarations

  • auto a1 = x;
    In accordance with $7.1.6.4, the compiler considers a function template like so:F = template<typename T> void f(T t);The compiler tries to instantiate the invented function template ‘f’ with the function callf(x);Now the rules for deducing function template arguments kicks in in accordance with $14.8.2.1P = T, which is the type of the function parameter ‘t’A = int, which is the type of the initializer expression ‘x’Therefore Deduced A = int

    Therefore the type of ‘a1’ is ‘int’

    Deducing A as int successfully instantiates the function template ‘f’ for the function call ‘f(x)’ and the declaration is well-formed

  • auto const a2 = x;
    In accordance with $7.1.6.4, the compiler considers a function template like so:F = template<typename T> void f(T const t);The compiler tries to instantiate the invented function template ‘f’ with the function callf(x);Now the rules for deducing function template arguments kicks in in accordance with $14.8.2.1P = T const, which is the type of the function parameter ‘t’A = int, which is the type of the initializer expression ‘x’In accordance with $14.8.2.1/3, the top level cv qualifiers of P are ignoredTherefore P = T, which is the type of the function parameter ‘t’

    Therefore Deduced A = int

    Therefore the type of ‘a2’ is ‘int const’.

    Deducing A as int successfully instantiates the function template ‘f’ for the function call ‘f(x)’, and the declaration is well-formed.

  • auto &a3 = 2;
    In accordance with $7.1.6.4, the compiler considers a function template like so:F = template<typename T> void f(T &t);The compiler tries to instantiate the invented function template ‘f’ with the function callf(2);Now the rules for deducing function template arguments kicks in in accordance with $14.8.2.1. “If P is a reference type, the type referred to by P is used for type deduction.“P = T &, which is the type of the function parameter ‘t’A = int, which is the type of the initializer expression ‘2’Therefore Deduced A = int

    Therefore, ‘a3’ is a reference to an ‘int’

    Deducing A as int fails to instantiate the function template ‘f’ for the function call ‘f(2)’. This is because, we try to bind a non-const lvalue reference to an lvalue. Hence, the declaration is ill-formed.

  • auto &a4 = lvrx;
    In accordance with $7.1.6.4, the compiler considers a function template like so:F = template<typename T> void f(T &t);The compiler tries to instantiate the invented function template ‘f’ with the function callf(lvrx);Now, the question is ‘What is the type of the initializer expression lvrx?’$5/5:
    If an expression initially has the type “reference to T” (8.3.2, 8.5.3), the type is adjusted to T prior to any further analysis. The expression designates the object or function denoted by the reference, and the expression is an lvalue or an xvalue, depending on the expression.This means that the type of the initializer expression is ‘int’.So, now, we have:

    P = T &, which is the type of the function parameter ‘t’

    A = int, which is the type of the initializer expression ‘lvrx’

    Therefore Deduced A = int

    Therefore, ‘a4’ is a reference to an ‘int’

    Deducing A as int successfully instantiates the function template ‘f’ for the function call ‘f(lvrx)’. This is because; it is perfectly fine to bind a non-const lvalue reference to an lvalue. Hence, the declaration is well-formed.

  • auto &&rvr1 = x;
    This is an interesting case which involves auto deduction of rvalue references. In accordance with $7.1.6.4, the compiler considers a function template like so:F = template<typename T> void f(T &&t);The compiler tries to instantiate the invented function template ‘f’ with the function callf(x);Now the rules for deducing function template arguments kicks in in accordance with $14.8.2.1.If P is a reference type, the type referred to by P is used for type deduction.P = T &&, which is the type of the function parameter ‘t’A = int, which is the type of the initializer expression ‘x’

    Therefore Deduced A = intTherefore, ‘rvr1’ is a rvalue reference to an ‘int’.

    But, hold on! . $14.8.2.1/3 further states that if P is an rvalue reference to a cvunqualified template parameter and the argument is an lvalue, the type “lvalue reference to A” is used in place of A for type deduction. So, our analysis above is not correct. Let’s try again in the light of the special deduction rule for rvalue reference to template parameter

    P = T &&, which is the type of the function parameter ‘t’

    A = int&, which is the type of the initializer expression ‘x’. Note it is no longer treated as ‘int’

    Therefore Deduced A = int&

    Therefore ‘rvr1’ is of type ‘int & &&’. Hmm, but what is this type? At a first glance it looks like an rvalue reference to ‘int &’.

    $8.3.2/5:
    There shall be no references to references, no arrays of references, and no pointers to references.

    In light of the above, C++ does not allow reference to reference. So what is ‘int & &&’?

    $8.3.2/6 has the answer:
    If a typedef (7.1.3), a type template-parameter (14.3.1), or a decltype-specifier (7.1.6.2) denotes a type TR that is a reference to a type T, an attempt to create the type “lvalue reference to cv TR” creates the type “lvalue reference to T”, while an attempt to create the type “rvalue reference to cv TR” creates the type TR.

    In our case, we are indeed dealing with a template type parameter T who’s type is deduced as ‘int &’. In our case, ‘T’ is ‘int &’ and ‘TR’ is same as ‘T’. In accordance with the above, an attempt to create the type rvalue reference to cv ‘int &’ (i.e. trying to create int & &&), creates the type ‘TR’ which is nothing but ‘int &’.

    This sounds confusing and can be summarized neatly as shown below:

    For a given type X:

    • X& &, X& &&, and X&& & all collapse to type X&
    • The type X&& && collapses to X&&

    This is known as the reference collapsing rule.

    Therefore, in light of the above discussion, ‘rvr1’ is an ‘lvalue reference to int’ and not an ‘rvalue reference to int’

    Deducing A as ‘int &’ successfully instantiates the function template ‘f’ for the function call ‘f(x)’. This is because, it is perfectly fine to bind a non-const lvalue reference to an lvalue. Hence, the declaration is well-formed.

  • auto &&rvr1 = 2;
    In accordance with $7.1.6.4, the compiler considers a function template like so:F = template<typename T> void f(T &&t);The compiler tries to instantiate the invented function template ‘f’ with the function callf(2);Now the rules for deducing function template arguments kicks in in accordance with $14.8.2.1. If P is a reference type, the type referred to by P is used for type deduction.“P = T &&, which is the type of the function parameter ‘t’A = int, which is the type of the initializer expression ‘2’Therefore Deduced A = intTherefore, ‘a3’ is a rvalue reference to an ‘int’. Deducing A as int successfully instantiates the function template ‘f’ for the function call ‘f(2)’. This is because; it is perfectly fine to bind a rvalue reference to an rvalue. Hence, the declaration is well-formed.

We will now consider one last example illustrating the template argument deduction process with the C++11 ‘auto’ keyword

  • auto const &&rvr2 = ci; // ill-formed
    This is once again an interesting case which involves auto deduction of rvalue references.In accordance with $7.1.6.4, the compiler considers a function template like so:F = template<typename T> void f(T const &&t);The compiler tries to instantiate the invented function template ‘f’ with the function callf(ci);Now the rules for deducing function template arguments kicks in in accordance with $14.8.2.1. If P is a reference type, the type referred to by P is used for type deduction.P = T const &&, which is the type of the function parameter ‘t’A = int const, which is the type of the initializer expression ‘ci’It is important to note that $14.8.2.1/3 applies here. But ‘P’ is not a cv qualified type. Also note that the ‘P’ is a rvalue reference to cvqualified (and not cvunqualified) template parameter even though the argument is an lvalue. Therefore the following does NOT apply “If P is an rvalue reference to a cvunqualified template parameter and the argument is an lvalue, the type “lvalue reference to A” is used in place of A for type deduction.”Therefore, the deduction process is governed by the following statement “If P is a reference type, the type referred to by P is used for type deduction.

    Therefore Deduced A = int

    Therefore ‘rrvr2’ is of type ‘rvalue reference to int’.

    Deducing A as int fails to instantiate the function template ‘f’ for the function call ‘f(ci)’. This is because, we can not bind a rvalue reference to an lvalue, even if it is a const lvalue. Hence, the declaration is ill-formed.

Before, we close, I would like to highlight that you can figure out the inner workings of the compiler and deduction process by explicitly making a call to the invented function template and instantiating it with the initializer expression. This may require appropriate compiler flags to be set. This is once again implementation specific behavior and your mileage may wary.

Let’s illustrate this with an example

 template<class T> void f(T &&t) {
     g(t);
 }
 int main() {
     auto &&a3 = 2;
     f(2); // explicit call to function template ‘f’
 }
 

The function call ‘g(t)’ is intentionally added to generate compiler diagnostics at compile time to see the deduced value of ‘T’. Here are the (partial) compiler diagnostics in VS2010 and g++ compiler respectively

From VS2010
see reference to function template instantiation ‘void f<int>(T &&)’ being compiled
1> with
1> [
1> T=int
1> ]

From g++
/home/saxena/TestCode/TestCpp11/main.cpp||In function ‘int main()’:|
/home/saxena/TestCode/TestCpp11/main.cpp|6|warning: unused variable ‘a3′ [-Wunused-variable]|
/home/saxena/TestCode/TestCpp11/main.cpp||In function ‘void f(T&&) [with T = int]‘:|
/home/saxena/TestCode/TestCpp11/main.cpp:7|8|instantiated from here|
/home/saxena/TestCode/TestCpp11/main.cpp|2|error: ‘g’ was not declared in this scope|
||=== Build finished: 1 errors, 1 warnings ===|

In the next post, we will look at some other finer points of the C++11 ‘auto’ type specifer, including some of the other legitimate uses along with the disallowed usages.

I will be glad to hear from you. Please do share your thoughts and feedback.

 
1 Comment

Posted by on November 19, 2012 in C++

 

Tags: , , , , ,

 
Follow

Get every new post delivered to your Inbox.