Section 9.3.  Object Generators
Team LiB
Previous Section Next Section

9.3. Object Generators

By this point in the book, you've probably grown somewhat comfortable with long nested template argument lists, but we're sure you haven't forgotten how unwieldy they can be. An object generator is a generic function used to deduce type information that might otherwise have to be written out the long way.

To see how that works, consider the following template, which composes two callable objects, f and g. The result is a new function object that, when invoked on an argument x, computes f(g(x)), yielding value of type R:



    template <class R, class F, class G>


    class compose_fg


    {


     public:


        compose_fg(F const& f, G const& g)


          : f(f), g(g)


        {}





        template <class T>


        R operator()(T const& x) const


        {


            return f(g(x));


        }





     private:


        F f;


        G g;


    };



The following example uses compose_fg to compute - sin2(x) for each element of a sequence.



    #include <functional>


    #include <algorithm>


    #include <cmath>





    float input[5] = {0.0, 0.1, 0.2, 0.3, 0.4};


    float output[5];





    float sin_squared(double x) { return std::sin(std::sin(x)); }





    float* ignored = std::transform(


        input, input+5, output,


      , compose_fg<float,std::negate<float>,float(*)(float)>(


           std::negate<float>(), &sin_squared


        )


    );



Whew, that compose_fg specialization certainly is an eyeful! It works, but it would probably have been easier to handcraft a neg_sin_squared function for this purpose than to use compose_fg. At least the result would have been more readable that way. Fortunately, we can avoid writing out most of the template parameters for compose_fg if we have an auxiliary object generator function:



    template <class R, class F, class G>


    compose_fg<R,F,G> compose(F const& f, G const& g)


    {


        return compose_fg<R,F,G>(f,g);


    }



The entire purpose of compose is to serve as a vehicle for the function template argument deduction mechanism. Now the TRansform call can be written:



    float* ignored = std::transform(


        input, input+5, seq2


      , compose<float>(std::negate<float>(), &sin_squared)


    );



Because the compiler can deduce the type of the required compose_fg specialization from the types of the arguments to compose, there's no need to write the type out explicitly. Your C++ standard library's bind1st and bind2nd function templates are similar generators, yielding objects of type binder1st and binder2nd, respectively.[2]

[2] The Boost Bind librarythe basis for an entry in the first C++ standard technical report (TR1)provides a much better way to do the same thing.

When used to their full potential, object generators can allow users to generate some truly terrifyingbut powerfultemplate types with a minimum of syntactic fuss. We'll learn more about how that works when we discuss type erasure later in this chapter.

    Team LiB
    Previous Section Next Section