4

Please consider the code below.

The template parameter is a handler class that must provide the function bar().

I'm using the Pimpl idiom to hide the implementation details of Foo. Prior to having a template parameter the constructor definition was in foo.cpp and all was good.

///////////    
// foo.h
///////////    

class Foo
{
public:
    template <class Handler>
    Foo(Handler& handler);

    void someFunction();

private:
    /** Private implementation details. */
    struct Impl;
    const std::unique_ptr<Impl> m_impl;
};

template <class Handler>
Foo::Foo(Handler& handler) : m_impl{new Impl{handler}}
{
}

///////////    
// foo.cpp
///////////    

#include "foo.h"

/** Encapsulates the private implementation details of Foo. */
struct Foo::Impl
{
public:
    Impl(Handler& handler) : m_handler(handler)
    {
    }

    void someOtherFunction()
    {
        m_handler->bar();
    }

private:
    Handler& m_handler;

    friend class Foo;
};

void Foo::someFunction()
{
    m_impl->someOtherFunction();
}

Once I introduced the template parameter I had to put the constructor in foo.h, which causes the following compiler error:

Allocation of incomplete type 'Foo::Impl'.

I understand the reason why I get the error but I can't think of a way around it and still use the Pimpl idiom.

Can anyone help?

ksl
  • 4,519
  • 11
  • 65
  • 106
  • 1
    Whenever you create a new instance of the object, you also need to include its full definition. I don't think there's any way around, other than making Handler a base class and using polymorphism. – Neil Kirk Mar 17 '15 at 15:38
  • 1
    Where is the definition of the type referred to by `Foo::Impl::m_handler`? – Oktalist Mar 17 '15 at 15:51
  • @Oktalist That will be somewhere else. What are you getting at? – ksl Mar 17 '15 at 16:01
  • 4
    The constructor of `Foo` passes its argument to the constructor of `Impl`. The constructor of `Impl` is not a template. How does that work? A precise discussion is difficult when the token `Handler` has two different meanings. – Oktalist Mar 17 '15 at 16:14
  • 1
    Or, in short, please provide code that produces your error, and only your error. And make it not suck: a template type called `Handler` and another type called `Handler`? Why not just take `Handler` as a non-template type? If your goal is to hide the implementation of `Handler`, why are users expected to know the types that can be used to construct it (!?). If not, why not expose it? – Yakk - Adam Nevraumont Mar 17 '15 at 17:51
  • @Oktalist I see. My example is incorrect. The constructor of `Impl` must also be a template. It is the same type as what is passed to `Foo`. – ksl Mar 18 '15 at 07:53
  • Yeah, templates need to be defined in the header, so of course you would not be able to define `Impl` in the `.cpp` file if it contains a template. – Oktalist Mar 18 '15 at 16:21

2 Answers2

3

You can solve this with or without virtual functions. The first option is somewhat easier: Use virtual functions, either for Handler

class HandlerBase { .... virtual void bar() = 0; ... };
class Foo { ... std::unique_ptr<HandlerBase> handler; ... };

or for Impl

class ImplBase { ... virtual void someFunction() = 0; }
class Foo { ... std::unique_ptr<ImplBase> m_impl; ... }
template<typename Handler> class Impl : public ImplBase { ... Handler& m_handler; ...};

I think these are relatively straightforward to implement. You can see that when using HandlerBase you don't even need the pimpl idiom.

The other alternative is to use the The Impossibly Fast C++ Delegates method:

class Foo
{
public:
    template <class Handler>
    explicit Foo(Handler& handler)
            : handler(&handler) {
        someFunctionInstance = someFunctionTemplate<Handler>;
    }

    void someFunction() {
        someFunctionInstance(handler);
    }

private:
    typedef void (*SomeFunctionType)(void* handler);

    void* handler;
    SomeFunctionType someFunctionInstance;

    template<typename Handler>
    static void someFunctionTemplate(void* handler) {
        static_cast<Handler*>(handler)->bar();
    }
};

The type of Foo remains independent of Handler. The assignment in the constructor creates a custom instance of the someFunctionTemplate which is able to call the actual Handler type. SomeFunctionInstance is a result of a template instantiation on Handler but since it's a static function its type is also independent of Handler.

However this solution is tricky and does contain a reinterpret_cast, it is still fully type-safe.

tamas.kenez
  • 7,301
  • 4
  • 24
  • 34
2

In general terms, you need some kind of type erasure. In practice, this means a function pointer or a virtual function (which is just a higher level abstraction implemented using a function pointer), as in the three solutions offered by @tamas.kenez.

std::function provides type erasure, typically implemented using a virtual function. Here is a alternate take on the problem using std::function:

////////////
// foo.h

class Foo
{
public:
    template <class Handler>
    Foo(Handler& handler) : Foo{ tag{},
                                 [&handler](){handler.bar();} }
    {}

    // ...

private:
    /** Private implementation details. */
    struct Impl;
    const std::unique_ptr<Impl> m_impl;

    struct tag {};
    Foo(tag, std::function<void()> bar);
};

////////////
// foo.cpp

struct Foo::Impl
{
    Impl(std::function<void()> bar) : m_bar{bar}
    {}

    std::function<void()> m_bar;

    // ...
};

Foo::Foo(Foo::tag, std::function<void()> bar) : m_impl{new Impl{bar}}
{}

I've used a tag type to disambiguate constructor overloads.

The lambda, which captures a reference to handler, is stored in the std::function so that the initialization of m_impl can be factored out of the template.

Oktalist
  • 14,336
  • 3
  • 43
  • 63