I have a rather frustrating problem with MSVC being a bit too eager to instantiate all the templates.
I have a number of classes that wrap a family of type-erased implementations of various similar types using the p_impl idiom. There is substantial overlap in the methods of these classes, so I'm using a CRTP templated base class to wrap the common methods on the "common interface" part, and then using the derived classes to wrap the remaining methods. All the methods essentially defer to the pointer to implementation to actually do the work, but there are some other bits of the methods that need things that, at the time the base template has to be included, cannot be known (cyclic dependencies). Ideally, one would extern template class the
Consider the following situation, greatly simplified:
// interface.h
struct CommonInterface {
virtual ~CommonInterface() = default;
virtual void common_method_impl() = 0;
};
// specific_interface.h
#include "interface.h"
struct SpecificInterface : public CommonInterface {
virtual void specific_method_impl() = 0;
};
// common_wrapper.h
class Metadata;
template <typename Interface, typename Derived>
class CommonWrapper {
protected:
std::unique_ptr<Interface> p_impl;
public:
// metadata method for dynamic dispatch for added complication
const Metadata& metadata() const;
Derived& common_method();
};
// Insufficient information to instantiate all these methods yet
// In practise this needs to be placed in a separate header
template <typename Interface, typename Derived>
const Metadata& CommonWrapper<Interface, Derived>::metadata() const {
// the actual mechanism is much more complicated.
static const Metadata md;
return md;
}
template <typename Interface, typename Derived>
Derived& CommonWrapper<Interface, Derived>::common_method() {
// I've used the definition of metadata here, this is not
// yet defined. (And cannot yet be.)
const auto& md = metadata();
metadata.check(static_cast<Derived&>(*this));
p_impl->common_method_impl();
return static_cast<Derived&>(*this);
}
// specific_wrapper.h
#include "specific_interface.h"
#include "common_wrapper.h"
// declare
class SpecificWrapper;
// Please don't instantiate this here.
extern template class CommonWrapper<SpecificInterface, SpecificWrapper>
// Define the type
class SpecificWrapper : public CommonWrapper<SpecificInterface, SpecificWrapper> {
using base_t = CommonWrapper<SpecificInterface, SpecificWrapper>;
public:
SpecificWrapper& specific_method();
};
// specific_wrapper.cpp
#include "specific_wrapper.h"
// other includes to define all the things that I need for the implementations.
// explicitly instantiate the template base class
template class CommonWrapper<SpecificInterface, SpecificWrapper>;
// Implement the methods
SpecificWrapper& SpecificWrapper::specific_method() {
/* Stuff that is defined now */
base_t::p_impl->specific_method();
return *this;
}
// Metadata.h
#include "specific_wrapper.h"
// In reality, Metadata is also a polymorphic object.
class Metadata {
public:
void check(const SpecificWrapper& obj) const;
// Metadata is responsible for constructing Wrappers,
// def'n of Metadata cannot appear until after SpecificWrapper.
SpecificWrapper construct() const;
};
Unfortunately I have two problems
- First, I need to export/import these classes from a DLL requiring
__declspec(dllexport)/__declspec(dllimport)which doesn't play nice withextern templates (for simplicity, I'm ignoring this fact for now). - Second, MSVC casually ignores the fact that these templates can't be immediately instantiated leading to a large number of C4661 warnings (suppressed by warning(ignore : 4661)). Clearly I'm missing something here, and I wonder if there is a more elegant solution to this kind of problem.