views:

215

answers:

1

Based on chapter 5 (Generalized Functors) from the book "Modern C++ Design,"

I'm trying to write a Functor template. Before asking me "why don't I just use Boost's bind or Loki straight up?" the simple answer is "because I want to learn."

That being said, I have followed the book, and also used the example code as a reference, but I keep comming up with compile time errors and I can't seem to understand why.

When I modify the book's example code to not use the threading models, the examples work fine. But my code doesn't. Here is the relevent code:

very basic TypeList, along with the TypeAt and Nulltype, EmptyType (also based on the book)

<TypeList.hpp>
---
class NullType
{};

class EmptyType
{};

#define TYPELIST_1(T1) TypeList<T1, NullType>
#define TYPELIST_2(T1,T2) TypeList<T1, TYPELIST_1(T2) >
#define TYPELIST_3(T1,T2,T3) TypeList<T1, TYPELIST_2(T2, T3) >


template < typename T, typename U >
struct TypeList
{
typedef T Head;
typedef U Tail;
};


namespace typelist
{

template <class TList, unsigned int i > struct TypeAt;
template <class Head, class Tail >
struct TypeAt<TypeList<Head, Tail>,0 >
{
typedef Head Result;
};

template <class Head, class Tail, unsigned int i >
struct TypeAt<TypeList<Head, Tail>, i >
{
typedef typename TypeAt<Tail, i-1>::Result Result;
};

template <class TList, unsigned int index,
typename DefaultType = NullType>
struct TypeAtNonStrict
{
typedef DefaultType Result;
};
}
---

Using these is of course the Functor

<Functor.hpp>
---
namespace functorPrivate
{
template<typename R>
struct FunctorBase
{
    typedef R ResultType;
    typedef EmptyType Param1;
private:
};
}

template <typename R, class TList>
class FunctorImpl;

template <typename R>
class FunctorImpl<R, NullType > : public functorPrivate::FunctorBase<R>
{
public:
typedef R ResultType;

virtual ~FunctorImpl(){}
virtual R apply() = 0;
};

template <typename R, typename P1>
class FunctorImpl<R, TYPELIST_1(P1) > : public functorPrivate::FunctorBase<R>
{
public:
typedef R ResultType;
typedef P1 Param1;

virtual ~FunctorImpl(){}
virtual R apply(P1) = 0;

};

template < class ParentFunctor, typename Fun>
class FunctionHandler : public ParentFunctor::Impl
{
private:
typedef typename ParentFunctor::Impl Base;
Fun fun;
public:
typedef typename Base::ResultType ResultType;
typedef typename Base::Param1 Param1;

FunctionHandler(const Fun& fun) :
    fun(fun)
{}
virtual ~FunctionHandler()
{}

virtual ResultType apply()
{
    return fun();
}

virtual ResultType apply(Param1 p1)
{
    return fun(p1);
}

};

template <typename R, class TList = NullType>
class Functor
{
public:
typedef TList ParamList;
typedef R ResultType;
typedef FunctorImpl<R, TList> Impl;
typedef typename Impl::Param1 Param1;

Functor() :
    impl(0)
{}
//Function / Handling functor type templated constructor
template<class Fun>
Functor(const Fun& fun) :
    impl(new FunctionHandler< Functor, Fun>(fun))
{}

//apply functions
R apply() 
{
    return (*impl).apply();
}

R apply(Param1 p1) 
{
    return (*impl).apply(p1);
}
private:
std::auto_ptr<Impl> impl;

};

---

When I try to use this like so:

---
class TestFunctor0
{
public:
int operator()()
{
    cout << "zero param functor" << endl;
    return 1;
}
};

//somewhere in main...

TestFunctor0 f;
Functor<int, NullType> command(f);
int res = command.apply();
CPPUNIT_ASSERT_EQUAL(1, res);
---

I get the following compile-time error:

../../../include/real/Functors.hpp: In member function ‘typename ParentFunctor::Impl::ResultType FunctionHandler<ParentFunctor, Fun>::apply(typename ParentFunctor::Impl::Param1) [with ParentFunctor = Functor<int, NullType>, Fun = TestFunctor0]’:
main.cpp:246:   instantiated from here
../../../include/real/Functors.hpp:74: error: no match for call to ‘(TestFunctor0) (EmptyType&)’
main.cpp:31: note: candidates are: int TestFunctor0::operator()()

Does anyone understand why I am seeing this problem? I am out of ideas. Thanks

+1  A: 

This problem has been solved. It turns out that in FunctionHandler, having the apply() functions be virtual was causing the problems. Once I removed the virtual keywords, everything went smoothly.

I'm still not quite sure WHY this changed things so drastically, but it solved the problem. My best guess to the reason is that by adding virtual, the compiler will try to populate the vtable with the correct functions, and since Param1 is /technically/ being defined by NullType, it will find that one?

Please correct me if I am wrong, this is simply my best buess with limited understanding.

Nik
Your guess is correct. With the functions being `virtual`, the compiler will try to instantiate all the functions, as it needs the addresses for the vtable.With non-`virtual` functions, the compiler only tries to instantiate the functions that are actually called.
Bart van Ingen Schenau