From a compilation problem: A glance into STL

本文深入探讨了STL中函数适配器的工作原理,特别是`ptr_fun`和`bind2nd`的使用限制及背后原因。通过具体示例解释了如何避免非法引用问题,并提供了一种解决方案。
 

               From a compilation problem: A glance into STL

                                                                                         paradin

                                                                                         2 008-04-06

 

related problem:

http://topic.youkuaiyun.com/u/20080401/17/94f50ade-22b5-4557-aa1d-896afbd8b9c4.html

We know that in STL there are two important utilities in the header file <functional>. One of them is ptr_fun which turns a function to a corresponding function object. The other is the family of so called function adapters which bind a function object with a particular value as one of the object’s (its operator()’s) fixed parameter. How ever when I practice with them it seems to me that there are some restrictions imposed on the innermost primary function. For instance suppose that I implement a Matrix class and I write a template function to do the matrix multiplication using the overloaded operator*:

 

#include <iostream>

#include <functional>      // for bind2nd and ptr_fun

#include “matrix.h”                     //for template class Matrix

using namespace std;

 

template <class T>

T Mul(const T& a, const T& b)

{

  T result = a * b;

  return result;

}

 

int main()

{

  Matrix a = Matrix<4, 4>::I;         // some particular matrix

  Matrix<4, 4> x;

  cin >> x;                        // now for example there’s a generated matrix at run time

  result = bind2nd(ptr_fun(&Mul<Matrix<4, 4> >), a)(x);   // mulitiply x with the particular a;

 

  int intval;

  intval = bind2nd(ptr_fun(&Mul<int>), 111)(10);                   // also test the primary type;

 

  cout << result;

  return 0;

}

 

Now every logic seems fine, but all the compilers I’ve seen so far generates error messges for the two tagged lines like this: error C2529: '_Right' : reference to reference is illegal

error C2529: '_Left' : reference to reference is illegal

 

Why’s there some reference to reference? The only place in our program where there’s a reference is the template function Mul(const T&, const T&). And If we remove the references :

 

template <class T>

T Mul(T a, T b)

{

  T result = a * b;

  return result;

}

 

and rebuild, things will be OK. But hey! why the compiler stop us using const references to pass the params? What if the object is large? We have to care about the effiency.

 

Problem probably occurs from the two STL functions and we can look them up in the header file. Since compiler producers now implement <funcional> in much the same way Here we use the one used by microsoft visual studio 2005 at hand to demonstrate this problem.

 

First we look up ptr_fun to see what it returns and how the convertion work is done.

// TEMPLATE FUNCTION ptr_fun

template<class _Arg, class _Result>

inline pointer_to_unary_function<_Arg, _Result, _Result (__stdcall *)(_Arg)>

ptr_fun(_Result (__stdcall *_Left)(_Arg))

{     // return pointer_to_unary_function functor adapter

       return (std::pointer_to_unary_function<_Arg, _Result, _Result (__stdcall *)(_Arg)>(_Left));

}

 

 

// TEMPLATE FUNCTION ptr_fun

template<class _Arg1, class _Arg2, class _Result>

inline pointer_to_binary_function<_Arg1, _Arg2, _Result, _Result(__stdcall *)(_Arg1, _Arg2)>

ptr_fun(_Result (__stdcall *_Left)(_Arg1, _Arg2))

{     // return pointer_to_binary_function functor adapter

return (std::pointer_to_binary_function<_Arg1, _Arg2, _Result, _Result (__stdcall *)(_Arg1, _Arg2)>(_Left));

}

 

As we can see there are two and only two types of ptr_fun to encapsulate primary functions with one parameter and two parameters respectivly. The passed-in function is named “_Left” and is again passed to some constructor. And they return a “functional adapter” called pointer_to_unary_functional and pointer_to_binary_function. We also look them up:

 

// TEMPLATE CLASS pointer_to_unary_function

template<class _Arg, class _Result, class _Fn = _Result (*)(_Arg)>

class pointer_to_unary_function : public unary_function<_Arg, _Result>

{     // functor adapter (*pfunc)(left)

public:

       explicit pointer_to_unary_function(_Fn _Left) : _Pfun(_Left)

       {     // construct from pointer

       }

 

       _Result operator()(_Arg _Left) const

       {     // call function with operand

              return (_Pfun(_Left));

       }

 

protected:

       _Fn _Pfun;     // the function pointer

};

 

 

 

 

// TEMPLATE CLASS pointer_to_binary_function

template<class _Arg1, class _Arg2, class _Result, class _Fn = _Result (*)(_Arg1, _Arg2)>

class pointer_to_binary_function : public binary_function<_Arg1, _Arg2, _Result>

{     // functor adapter (*pfunc)(left, right)

public:

       explicit pointer_to_binary_function(_Fn _Left) : _Pfun(_Left)

       {     // construct from pointer

       }

 

       _Result operator()(_Arg1 _Left, _Arg2 _Right) const

       {     // call function with operands

              return (_Pfun(_Left, _Right));

       }

protected:

       _Fn _Pfun;     // the function pointer

};

 

As we can see pointer_to_unary_function and pointer_to_binary_function are actually function object class constructed by storing the primary function pointer. What’s the problem with it or with ptr_fun? In fact, there isn’t a problem. If we merely use ptr_fun and stop adapting it further there will not be problems:

 

result = ptr_fun(&Mul<Matrix<4, 4> >)(x, a);   // compilation ok;

intval = ptr_fun(&Mul<int>)(10, 111);               // compilation ok;

 

So it’s the bind2nd function’s problem. Before we look it up there’s one more thing you might have noticed: pointer_to_unary_function and pointer_to_binary_function is inherited from some unary_function and binary_function which seems useless at the moment. But in fact all STL function object classes need to be inherited from them in order to pass on type infos when adapted by other adaptors like bind2nd. We’ll see it clearly after we study bind2nd.

 

// TEMPLATE FUNCTION bind2nd

template<class _Fn2, class _Ty>

inline binder2nd<_Fn2> bind2nd(const _Fn2& _Func, const _Ty& _Right)

{     // return a binder2nd functor adapter

       typename _Fn2::second_argument_type _Val(_Right);

       return (std::binder2nd<_Fn2>(_Func, _Val));

};

 

We can see that in the use of bind2nd(ptr_fun(…), …), _Fn2 == pointer_to_unary_function<…> || pointer_to_binary_function<….>. Again you might have guessed that this function return another function object class storing the primary function object we passed in(_Func) and the second fixed parameter value(_Right) we want to bindwith. But here’s the problem: how we do get the exact type of the original function’s params and return value? We need to pass them onto the next level of encapulation(Otherwise how are we to implement the returned object’s operator()?). Because it is a class we pass in(class _Fn2), we cannot extract the original types directly. One solution is to store the type infos in _Fn 2 in a way we can obtain conveniently. The easiest way is typedef. The first line roughly confirmed our guess:

typename _Fn2::second_argument_type _Val(_Right);

// cast _Right to the correct parameter type stored in _Fn2

 

But when are the type infos stored and how? That’s what struct unary_function and binary_function’s up to:

 

// TEMPLATE STRUCT unary_function

template<class _Arg, class _Result>

struct unary_function

{     // base class for unary functions

       typedef _Arg argument_type;

       typedef _Result result_type;

};

 

// TEMPLATE STRUCT binary_function

template<class _Arg1, class _Arg2, class _Result>

struct binary_function

{     // base class for binary functions

       typedef _Arg1 first_argument_type;

       typedef _Arg2 second_argument_type;

       typedef _Result result_type;

};

 

All function object classes should derive from the two template structs in order to store original type infos in order for further adaptions.

 

With all these ideas let’s check the returned function object class binder2nd:

 

// TEMPLATE CLASS binder2nd

template<class _Fn2>

class binder2nd : public unary_function<typename _Fn2::first_argument_type,

typename _Fn2::result_type>

{     // functor adapter _Func(left, stored)

public:

       typedef unary_function<typename _Fn2::first_argument_type,

       typename _Fn2::result_type> _Base;

       typedef typename _Base::argument_type argument_type;

       typedef typename _Base::result_type result_type;

    /* I think we can write _Fn2::result_type as well. */

       binder2nd(const _Fn2& _Func, const typename _Fn2::second_argument_type& _Right)

              : op(_Func), value(_Right)

              {     // construct from functor and right operand

              }

 

       result_type operator()(const argument_type& _Left) const // note the param return types

       {     // apply functor to operands

              return (op(_Left, value));

       }

 

       result_type operator()(argument_type& _Left) const     // note the param and return types

       {     // apply functor to operands

              return (op(_Left, value));d

       }

 

protected:

       _Fn2 op; // the functor to apply

       typename _Fn2::second_argument_type value;     // the right operand

};

 

Now we understand this ‘typedef’ technique is heavily used in STL and why it should be on these conditions. It’s clear now how this ‘bind2nd(ptr_fun(…))’ thing goes: keeps generating function object using current result with original typeinfo stored at every layer. The compiler surely does at lot of work at resloving templates. Now let ourselves be a compiler to do the work manuly to completely set clear the whole thing!

 

template <class T>

T Mul(T a, T b)

{

  T result = a * b;

  return result;

}

 

int main()

{

      

int intval;

intval = bind2nd(ptr_fun(&Mul<int>), 111)(10);    

}

 

First, the int version of Mul is instantiation:

      int Mul(int, int)

Now _Arg1 = _Arg2 = _Result = int

Then it’s passed to the second version of ptr_fun:

pointer_to_binary_function<int, int, int, int (*)(int, int)> ptr_fun<int, int, int>;

 

Now a pointer_to_binary_function object is created with Mul<int> and the types are stored in its base class binary_function<int, int, int, int(*)(int, int)>, so in this instantiation:

 

pointer_to_binary_function<int, int, int, int(*)(int, int)>::first_argument_type == int

pointer_to_binary_function<int, int, int, int(*)(int, int)>::second_argument_type == int

pointer_to_binary_function<int, int, int, int(*)(int, int)>::result_type == int

 

Now the returned pointer_to_binary_function object(holding the porinter to Mul<int>) is further passed on to bind2nd. Then we have to instantiate it like this:

 

bind2nd< pointer_to_binary_function<int, int, int, int(*)(int, int)>, int>

and the returned binder2nd:

 

binder2nd< pointer_to_binary_function<int, int, int, int(*)(int, int)> >

binder2nd< pointer_to_binary_function<int, int, int, int(*)(int, int)> >::argument_type

== pointer_to_binary_function<int, int, int, int(*)(int, int)>::first_argument_type == int

binder2nd< pointer_to_binary_function<int, int, int, int(*)(int, int)> >::result_type

== pointer_to_binary_function<int, int, int, int(*)(int, int)>::result _type == int

 

and the final important thing we want:

 

// result_type -> int

// const argument_type& -> const int&

 

       int operator()(const int& _Left) const // type replaced. Note that an extra & is appended

       {     // apply functor to operands

              return (op(_Left, value));

       }

 

       //result_type -> int

    //argument_type& -> int&

       int operator()(int& _Left) const // type replaced. Note that an extra & is appended

       {     // apply functor to operands

              return (op(_Left, value));d

       }

 

So finialy we have a completed instantiated binder2nd function object that meets our need.

 

OK, now let’s go back to our problem at the beginning: why we can not use reference as params:

 

template <class T>

T Mul(const T& a, const T& b)

{

  T result = a * b;

  return result;

}

 

int main()

{

      

int intval;

intval = bind2nd(ptr_fun(&Mul<int>), 111)(10);    

}

 

The compiler then deduces types and instantiates templates as before. When binder2nd is instantiated, the argument_type with it is, according to our analysis, const int&, and result_type = int. So we are demanding the comipler to generate such an overloaded operater():

 

// result_type -> int

// const argument_type& -> const const int&&

       int operator()(const int&& _Left) const // type replaced. Note that an extra & is appended

       {     // apply functor to operands

              return (op(_Left, value));

       }

 

       //result_type -> int

    //argument_type& -> const int&&

       int operator()(int&& _Left) const     // type replaced. Note that an extra & is appended

       {     // apply functor to operands

              return (op(_Left, value));d

       }

The repeated “const” should be no problem but C++ does not surport multiple reference simbol “&” followed after some type! I.e, the const const int&& is illegal syntax! And that’s why the compiler complains that reference to reference is illegal. Oops, at last!

 

This problem seems to be a ubiqitous one across many compilers. Possibly a STL design pitfall. It is unreasonable and funny in an aspect that for itself to grarantee its own efficiency(that’s why a reference is used) it forbids programmer care about the wrapped-in function’s effiencey(we cannot pass by reference). But of course we can just pass by pointers to reach the same purpose. Fortunately it works well with all other function objects, and our case is  not that common in practices. (In fact, it seems to me it have to be designed this way in order to work with those predifined STL function object classes if you know how those classes are defined.)

 

If we must pass by reference (rather than by pointer) is there a way to resolve this problem within stardard c++? In the meaning of pass-by-reference I still can not find any. But in the meaning of successful compilation there is one: write an other function “adapter” none_ref_ptr_fun to remove referenceness before passed to ptr_fun. The code will seem like this:

 

template <class T>

T Mul(const T& a, const T& b)

{

  T result = a * b;

  return result;

}

 

int main()

{

      

int intval;

intval = bind2nd(ptr_fun(none_ref_ptr_fun(&Mul<int>)), 111)(10);   

}

 

Here I represent a implemented version of none_ref_ptr_fun(binary function only) provided by my friend Yorksen:

 

/*

  function adapter implementation to remove referenceness.

  binary_function version only.

  by Yorksen, 2008-04-02

*/

 

 

#include <cstdlib>

 

#include <iostream>

#include <functional>

using namespace std;

 

// function type traits template

template<class T>

struct FunTraits

{};

 

// half instiation

template<class T,class U,class RET>

struct FunTraits<RET(*)(T,U)>

{

   typedef RET RET_TYPE;

   typedef T   PARAM1_TYPE;

   typedef U   PARAM2_TYPE;   

};

 

// half instiation

template<class T,class U,class RET>

struct FunTraits<RET(*)(T&,U)>

{

   typedef RET RET_TYPE;

   typedef T   PARAM1_TYPE;

   typedef U   PARAM2_TYPE;   

};

 

// half instiation

template<class T,class U,class RET>

struct FunTraits<RET(*)(T,U&)>

{

   typedef RET RET_TYPE;

   typedef T   PARAM1_TYPE;

   typedef U   PARAM2_TYPE;   

};

 

// half instiation

template<class T,class U,class RET>

struct FunTraits<RET(*)(T&,U&)>

{

   typedef RET RET_TYPE;

   typedef T   PARAM1_TYPE;

   typedef U   PARAM2_TYPE;   

};

 

 

// function adapter

template<class T>

struct Fun_Adp

{

   typedef typename FunTraits<T>::RET_TYPE    RET_TYPE;

   typedef typename FunTraits<T>::PARAM1_TYPE PARAM1_TYPE;

   typedef typename FunTraits<T>::PARAM2_TYPE PARAM2_TYPE;

   typedef RET_TYPE (*FPOINTER_TYPE)(PARAM1_TYPE,PARAM2_TYPE);

  

   static T m_pfun;

   explicit Fun_Adp(T pfun)

   {

        m_pfun = pfun;

   }

  

   // use a static function to be the returned new function

   static RET_TYPE SimFun(PARAM1_TYPE p1, PARAM2_TYPE p2)

   {

       return m_pfun(p1,p2);

   }

  

   FPOINTER_TYPE ToFunPtr()

   {

       return SimFun;

   }

};

 

template<class T>

T Fun_Adp<T>::m_pfun;

 

template<class T>

Fun_Adp<T> ref_fun_adp(T pFun)

{

    return Fun_Adp<T>(pFun);

}

 

 

template<class T>

typename Fun_Adp<T>::FPOINTER_TYPE none_ref_ptr_fun(T pFun)

{

    return ref_fun_adp(pFun).ToFunPtr();

}

 

template <class T>

T Mul(const T& a, const T& b)

{

  T result = a * b;

  return result;

}

 

int main()

{

int intval;

intval = bind2nd(ptr_fun(none_ref_ptr_fun(&Mul<int>)), 111)(10);   // everything ok

       return 0;

}

 

Note how template trait technique is used here. Because ptr_fun only accepts primary function rather than function object, our none_ref_ptr_fun adapter is a bit differenct in that it must return a new primary function. We use class static function SimFun (in which the primary function is invoked) as the return value.

 

As you might have found out the truth of the trick: we just create an extra new pass-by-value function merely to cater for the compiler. It's cheating. The effiency problem is still there. As we never want to be a slave of compiler, we just abandon the whole thing:

 

cout   < < boost::bind(&Mul<int>, _1, 111)(10)   < < endl; 

// provided by taodm

 

Wanna see how it’s implemented? Just go find one and have fun!

Probaby the powerful boost library will be part of the next standard.

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值