0

I am trying to combine the "pointer to implementation" (pImpl) idiom with variadic template arguments. However, I'm encountering a linker error, which I suspect might be due to a missing instantiation.

Here is a simplified version of my code:

foo.h

#pragma once

class Foo
{
public:
    Foo();

    template <class... Args>
    int Method(Args&&... args);

private:
    class Impl;
    Impl* m_Impl;
};

foo.cpp

#include <utility>

#include "foo.h"
#include "impl.h"

Foo::Foo() :
    m_Impl(new Foo::Impl())
{
}

template <class... Args>
int Foo::Method(Args&&... args)
{
    return m_Impl->Method(std::forward<Args>(args)...);
}

impl.h

#pragma once

#include "foo.h"

class Foo::Impl
{
public:
    int Method(int argc, char* argv[]);
};

impl.cpp

#include <iostream>

#include "impl.h"

int Foo::Impl::Method(int argc, char* argv[])
{
    for (int i = 0; i < argc; ++i)
        std::cout << argv[i] << std::endl;
    return EXIT_SUCCESS;
}

main.cpp

#include "foo.h"

int main(int argc, char* argv[])
{
    Foo foo;
    return foo.Method(argc, argv);
}

My goal is to maintain the separation between Foo and Foo::Impl. However, the linker reports that it cannot find the symbol int Foo::Method<int&, char**&>(int&, char**&) (see example here). I've tried multiple method signatures to instantiate the missing symbol, but haven't been able to resolve the issue. There seems to be a subtlety in the way this is handled that I cannot fully understand. If the issue is indeed due to a missing instantiation, hopefully it can be resolved within the impl files to preserve the separation and avoid contaminating the foo files. Thank you!

4
  • 2
    Since Impl::Method takes exactly two parameters of known types, why is Foo::Method a template (variadic or otherwise)? Why doesn't it take the same two parameters of the same types? Can you show an example where Foo::Method being a template makes something work that wouldn't work otherwise? Commented Aug 3 at 3:44
  • This question is really unrelated to the pimpl idiom, and could be simplified if you took it away. From what I can see the question is really how to convert a variadic template argument list to a argc/argc pair. And since you call the variadic template function using an argc/argv pair, why is the function a variadic template to begin with? Commented Aug 3 at 3:47
  • And if you really want to go C++ all the way, you can very easily convert the argc/argv pair to a std::vector<std::string>. Commented Aug 3 at 3:49
  • The Foo::Impl::Method parameters of types int and char** are just examples. Imagine those can be anything, platform-specific, that I do not want to leak in foo.h. Commented Aug 3 at 4:03

1 Answer 1

2

The problem is that you're putting the implementation of the template in the .cpp file, so when Foo::Method<int&, char**&> is instantiated in main.cpp it can't find the definition. The reason it can't find it is because templates aren't compiled until their definition is needed and there wasn't an instantiation of the function in impl.cpp to cause it to be complied. To fix this you can make foo.cpp explicitly instantiate the function with the parameters you're calling it with.

template int Foo::Method(int&, char**&);

This will allow it to work without putting the definition in the .h file. It comes at the cost of having to supply the exact types.

Sign up to request clarification or add additional context in comments.

2 Comments

Already tried this, it works if I put it in foo.cpp, but it doesn't if I put it in impl.cpp because it cannot find the definition, see godbolt.org/z/13nMaTWGr.
Because that's the wrong place to put it. You have to put it in the same file that contains the definition, which is foo.cpp.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.