Solved

Function Objects

Posted on 2004-08-20
6
236 Views
Last Modified: 2013-12-14

In my quest to understand function objects, portions of the code below is a mystery to me.  So now:

# include <iostream>
# include <ostream>
# include <string>
# include <sstream>
# include <vector>
# include <algorithm>

struct Split
 {
  std::string Buffer;
  std::vector<unsigned char> Vec;

  Split( std::string Src ) : Buffer( Src )
   {
    std::istringstream Iss( Buffer );

    while( Iss.good() && std::getline( Iss, Buffer, '.' ) )
           Vec.push_back( std::strtoul( Buffer.c_str(), 0, 10 ) );
   }

  void operator() ( char ) const {}

  operator std::vector<unsigned char>() const { return Vec; }
 };

int main()
 {
  std::string Src( "1.3.6.1.5.1.11.3.2.1.4.25.0" );

  std::vector<unsigned char> Vec =
       std::for_each( Src.begin(), Src.end(), Split( Src ) );

  std::copy( Vec.begin(), Vec.end(),
             std::ostream_iterator<int>( std::cout, "\n" ) );

  return 0;
 }

I understand the basic premise behind the call to for_each, which involves calling Split for each Src element from begin to end (or one past end).  Debuging the code shows a different story.

First the constuctor for Split gets called.  getline removes the token '.' and the object now contains 13 elements.

operator() (char)  gets called twenty seven (27) times.  I know because I placed  a static counter within operator().  This was a mystery to me, but then I discovered that each element within Src is treated as such.  A char.  So when operator() (char) gets called twice for '11' etc.

The confusion  stems from the fact that the code within the constructor  - as I understand it - has partitioned the Src object and placed the result in Vec.  What's the net gain for calling Split 27 times.  In essence operator() (char) isn't doing much or am I mistaken?

Consider:
  operator () (char)
  operator std::vector<unsigned char>() const { return Vec; }

The primary (used sparingly) difference between the two function objects is the latter specifies the return type while the former does not?  The latter is needed for visibility into Vec contained in Split?

Thanks in advance
0
Comment
Question by:forums_mp
  • 2
  • 2
  • 2
6 Comments
 
LVL 19

Assisted Solution

by:drichards
drichards earned 20 total points
ID: 11852795
>> I understand the basic premise behind the call to for_each, which involves calling Split for each Src element from begin to end >> (or one past end).  Debuging the code shows a different story
No, you have misunderstood how the for_each works.  It calls the function for each element of Src from Src.begin() to Src.end().  For a string, the iterator goes one character at a time, so for each character in Src (there are 27 characters) 'operator()(char)' is called.

The end result is that the for_each calls the NO-OP operator a bunch of times.  You would achieve the same result with:

   std::string Src( "1.3.6.1.5.1.11.3.2.1.4.25.0" );
   std::vector<unsigned char> Vec = Split(Src);
   std::copy( Vec.begin(), Vec.end(),std::ostream_iterator<int>( std::cout, "\n" ) );

The really important part of your for_each is to construct the Split struct.  The iteration of for_each chews up some CPU cycles but doesn't accomplish anything.

And you do not have two function objects.  One function object with a cast operator and the 'function' operator.
0
 

Author Comment

by:forums_mp
ID: 11853922

So all the 'works' was been done the first time via the call to Split constructor.  Right?

I assume by NO-OP operator you mean operator () (char)?  I realize operator () (char) is doing nothing which I suspect constitues NO-OP, well......

I might add that I've not see anything about function operator in Jousittis but what makes this
  operator char*() { return "test" ; }
a function operator?

IOW, I interpret the above to mean.  An 'unnamed' function returning a char*.  The addition of the operator keyword adds a new 'dimension' but I'm unsure what.
0
 
LVL 19

Expert Comment

by:drichards
ID: 11855145
>> So all the 'works' was been done the first time via the call to Split constructor.  Right?
Exactly.  Merely constructing the object performs the split into the vector.

A function object (in this context) is nothing more that an object that implements an operator() with appropriate parameters.  In for_each, the parameter list needs to be a single parameter of whatever type the iterator dereferences to.  In your case it is char, so you provide 'operator() (char)'.

>>  The addition of the operator keyword adds a new 'dimension' but I'm unsure what.
An operator is not unnamed as you could call an operator as 'a.operator+(b)' rather than saying 'a+b' if you really wanted.  The difference is that operators may be called implicitly.
0
Courses: Start Training Online With Pros, Today

Brush up on the basics or master the advanced techniques required to earn essential industry certifications, with Courses. Enroll in a course and start learning today. Training topics range from Android App Dev to the Xen Virtualization Platform.

 
LVL 15

Expert Comment

by:efn
ID: 11857357
>> what makes this
>>  operator char*() { return "test" ; }
>>a function operator?

It's not a function operator, it's a conversion operator.  A function call operator is operator() (possibly with some parameters), so you could declare a function call operator that returns char* like this:

char* operator();

--efn
0
 

Author Comment

by:forums_mp
ID: 11877072

Here's I suspect one last question/example on this.  For some reason I still struggle with the function object syntax.  Consider

# include <iostream>
# include <ostream>

template<class NumT> struct limit_range
 {
  NumT Value;
  NumT MinValue;
  NumT MaxValue;

  explicit limit_range( NumT value, NumT Tmin, NumT Tmax )
           : Value( value ), MinValue( Tmin ), MaxValue( Tmax ) {}

    operator int() const
     {
      return  ( Value >= MinValue ) && ( Value <= MaxValue ) ?
                Value :
                throw std::runtime_error( "Value is out of range.\n" );
     }
 };

class SomeObject
 {
  private:
     int N;
  public:
     SomeObject( int n ) : N( limit_range<int>( n, 100, 200 ) ) {}

     void SetValue( int n ) { N = limit_range<int>( n, 100, 200 ); }
 };

int main()
 {
  try
   {
    SomeObject A( 150 );
    SomeObject B( 200 );

    B.SetValue( 201 );
   }
  catch( const std::runtime_error& e )
   {
    std::cerr << "ERROR: " << e.what() << std::endl;
   }

  return 0;
 }

How does the compiler transform the constructor call  "limit_range<int>( n, 100, 200 )" into operator int().
I'm thinking.  The constructor gets called and now I'm left with N().  However, N is not of type limit_range so how does the operator int() gets called?
0
 
LVL 15

Accepted Solution

by:
efn earned 30 total points
ID: 11877401
limit_range<int>( n, 100, 200 )

is not really a constructor call, it tells the compiler to construct a temporary limit_range object.  In general, if a class C has a default constructor, you can code C() to specify a temporary object of that class, and if it has a constructor with parameters, you can pass parameters in the parentheses.

So

 N( limit_range<int>( n, 100, 200 ) )

initializes the int N with the temporary limit_range object.  The compiler knows N is an int, so it expects an int to initialize it.  The limit_range object is not an int, but it has a conversion operator that converts to int, so the compiler automatically converts the limit_range to an int and uses the int to initialize N.  The general principle is that if you use an object of class C in a context that calls for an object of another type D, and C has a conversion operator that converts a C to a D, the compiler will automatically use the conversion operator.

There is no function object anywhere in this example.  For a type T,

T operator()

declares a function call operator that returns a T, while

operator T()

declares a conversion operator that converts to T.

--efn
0

Featured Post

Announcing the Most Valuable Experts of 2016

MVEs are more concerned with the satisfaction of those they help than with the considerable points they can earn. They are the types of people you feel privileged to call colleagues. Join us in honoring this amazing group of Experts.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

When writing generic code, using template meta-programming techniques, it is sometimes useful to know if a type is convertible to another type. A good example of when this might be is if you are writing diagnostic instrumentation for code to generat…
Basic understanding on "OO- Object Orientation" is needed for designing a logical solution to solve a problem. Basic OOAD is a prerequisite for a coder to ensure that they follow the basic design of OO. This would help developers to understand the b…
The viewer will be introduced to the technique of using vectors in C++. The video will cover how to define a vector, store values in the vector and retrieve data from the values stored in the vector.
The viewer will be introduced to the member functions push_back and pop_back of the vector class. The video will teach the difference between the two as well as how to use each one along with its functionality.

815 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

11 Experts available now in Live!

Get 1:1 Help Now