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

被折叠的 条评论
为什么被折叠?



