Purpose:
In this post I attempt to explain this piece of C++ code
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');.
Motivation:
Many times I have been asked on forums to explain this piece of code. The problem is that this piece of code takes advantage of several concepts, that may be hard for a beginner to understand. Templates, Namespaces, static methods all come in to play here. In order to explain this piece of code we first have to explain these concepts. Which is difficult to do in a forum post.
Namespaces:
In C++ namespaces are a named scope. The scope begins with a open brace { and ends with a closing brace }. Namespaces are used to prevent name clashes. Lets look at an example.
#include <iostream>
namespace Foo
{
int foo = 5;
}
namespace Bar
{
int foo = 10;
}
int main()
{
std::cout << Foo::foo << std::endl;
std::cout << Bar::foo << std::endl;
return 0;
}
Here we have two namespaces. One called Foo and the other called Bar. Each of these namespaces has an integer variable called foo. Because these variables are in different namespaces the compiler can tell them apart. We tell the compiler we want to access something in a namespace by using the name of the namespace and the scope resolution operator. For example, Foo::foo means we want to access the integer variable foo in namespace Foo, and Bar::foo means we want to access the integer variable foo in namespace Bar. To the compiler these variables are completely different even though they have the same name.
static methods:
In C++ static methods are methods of a class that can be called without a calling object. Take a look at this program.
#include <iostream>
class Foo
{
public:
void hello()
{
std::cout << "Hello from foo " << std::endl;
}
};
int main()
{
Foo objectOfFoo; //create an object of class Foo
objectOfFoo.hello(); //use the Foo object to call the hello method
return 0;
}
In this program we create on object of class Foo named objectOfFoo. We then use that object to call the hello() method of the class Foo. When we use objectOfFoo to call a method of class Foo, we say that objectOfFoo is the calling object. We need an object of class Foo to call the method hello(), because the compiler needs to know where to look for the definition of the hello() method. When we call hello() with an object of class Foo the compiler looks in the Foo class for the definition of
hello().
Lets look at another example.
#include <iostream> class Foo { public: void hello() { std::cout << "Hello from foo " << std::endl; } }; class Bar { public: void hello() { std::cout << "Hello from bar" << std::endl; } }; int main() { Foo objectOfFoo; //create an object of class Foo objectOfFoo.hello(); //use the Foo object to call the hello method Bar objectOfBar; //create on object of the Bar class objectOfBar.hello(); //call the hello method of the Bar class return 0; }
In this example we have added a class Bar with a method called hello(), just like class Foo. So how does the compiler know which definition to use when calling the method hello()? Well its simple, when the method is being called by an object of class Bar, then the definition of hello() in class Bar is used. When the method is being called by an object of class Foo then the definition of hello() in class Foo is used.
Static method are methods that can be called without a calling object. However, the compiler still needs to know where the definition of the static method is. So when we call a static method we specify a namespace. The compiler then looks in this namespace for the definition of the static method.
Example:
#include <iostream>
class Foo
{
public:
static void hello() //hello is now static
{
std::cout << "Hello from foo " << std::endl;
}
};
int main()
{
Foo::hello(); // we no longer need to create an object of class Foo
return 0;
}
In this example we call the hello method of class Foo, but we don't use an object of class foo. Instead we specify the namespace by using the name of the class and the scope resolution operator Foo::. This tells the compiler that the definition of the method hello() is in the Foo namespace. So the compiler knows where to look for it.
Now lets take a look at calling a static method from a template class.
Example:
#include <iostream>
template<class T>
class Foo
{
public:
static void hello() //hello is now static
{
std::cout << "Hello from foo " << std::endl;
}
};
int main()
{
Foo<int>::hello();
return 0;
}
In this example we use a template class to call the static method hello() from class Foo. Because class Foo is now a template we have to specify a template argument. We do this by adding <int> to the namespace specification. In this example it doesn't matter what we pass as the template argument because class Foo doesn't actually do anything with it, but it is required simply because its a template.
std::streampos:
streampos is simply an integer data type. We don't know the exact size std::streampos can represent, because this is implementation specific. We also don't care about the exact size of std::streampos. What really matters is that std::streampos can represent the maximum size of a stream, whatever that happens to be on your machine.
Now that we have learned a little bit lets take a look at this piece of code
std::cin.ignore(std::numeric_limits<std::streamsize>::max());. The part that confuses people is std::numeric_limits<std::streamsize>::max(). So lets start with this. std::numeric_limits is a template class which is inside the limits header file eg. #include <limits>. Because numeric_limits is a template class we need to specify a template parameter. which is why we see <std::streamsize> after numeric_limits. The numeric_limits class contains a static function called max. It looks something like this.
template<class T>
numeric_limits
{
static T max();
};
What is important to note here is that the template parameter becomes the return type of the static method max.
std::numeric_limits<std::streamsize>:: this portion of the code just specifies a namespace of the template class numeric limits. Remember that when we call a static method we need to provide the namespace of the static method. therefore this std::numeric_limits<std::streamsize>::max() portion of the code is simply calling the max method contained within the numeric_limits<std::streamsize> namespace.
The max() method itself returns the maximum size of its template parameter. Which in this case happens to be std::streamsize. std::streamsize is at least as big as the maximum size of our stream. Therefor when we call max on std::streamsize the result is a number that is at least the same size of our stream.
So what this code std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); actually says is ignore all characters in std::cin up to and including the newline character.
No comments:
Post a Comment