I am trying to explicitly instantiate a member function template, to keep its definition out of the header. The types that need to be instantiated for are the alternative types of an std::variant instantiation.
I would like to avoid typing all the explicit instantiations in the source file.
Minimum exposition code:
Header:
#include <variant>
using VarType = std::variant<bool, int, float>;
class VarHolder
{
public:
template<typename T>
const T* get() const;
private:
VarType var;
};
Source:
// A trivial function, for demonstration only
template<typename T>
const T* VarHolder::get() const
{
if (std::holds_alternative<T>(var))
return &std::get<T>(var);
return nullptr;
}
// manual instantiations, which i'd like to avoid,
// since I need to keep these in sync with the definition of VarType
//template const bool* VarHolder::get<bool>() const;
//template const int* VarHolder::get<int>() const;
//template const float* VarHolder::get<float>() const;
My attempts
So far I've come up with two possible solutions:
- Define a function that calls all required instantiations
template<class T>
struct F;
template<class ...Ts>
struct F<std::variant<Ts...>> {
static void f() {
VarHolder t{};
((t.get<Ts>()), ...);
}
};
template struct F<VarType>; // actual instantiation
This works in MSVC 2022 (C++17). I'm able to include the header and call all the instantiations of VarHolder::get(), without getting linker errors. However I doubt it's the optimal way.
- Storing function pointers.
Since I'm not familiar with member function pointers, I decided to create a wrapper function template, which will call the get() method for some type.
template<typename T>
void fn(const VarHolder& t)
{
t.get<T>();
}
using fn_t = decltype(&fn<int>); // should be void (*)(const VarHolder&)
template<typename T>
struct S;
template<typename ...Ts>
struct S<std::variant<Ts...>> {
constexpr static fn_t arr[]{ (&fn<Ts>)... }; // store pointers to all instantiations of fn()
};
template struct S<VarType>; // actual instantiation
This doesn't work in MSVC 2022. I get a linker error if calling VarHolder::get() elsewhere.
Questions:
- Is the 1st method actually portable? Is a conforming compiler allowed to not instantiate?
- Why does the 2nd method fail? I thought taking function pointers should force instantiate the function template.
- Is there a more elegant/portable way to achieve my goal?
You must explicitly instantiate the member function template.
- [temp.pre] p10
You're trying to "cheat the system" by generating explicit instantiations implicitly, but that's not possible.
template F<VarType>;(which should betemplate class F<VarType>) attempts to explicitly instantiateF, which then generates all those explicit instantiations for you. However, this is not portable. If it works, it works because MSVC is unnecessarily aggressive when it comes to transitive explicit instantiations.[temp.explicit] p10 states that only the direct non-template members will be explicitly instantiated when a class template is instantiated.
fis obviously not a direct non-template member ofF, so this approach won't work.Taking addresses of function templates by forming a function pointer is also not an explicit instantiation, so this would violate the first quote.
Minimal reproducible example
Let's look at an example:
This explicit instantiation of
Salso includes an implicit instantiation offoo<int>(). The generated assembly from GCC or Clang includes only: (https://godbolt.org/z/orffnM7eT)MSVC does emit
foo<int>for some reason, but I wouldn't rely on it. If onlyS<int>is emitted andfoo<int>is inlined and not found in the assembly, you get a linker error when callingfoo<int>from another object file, since it doesn't exist.Presumably, that's why your second method gives you a linker error.
Workaround using macros
Macros are ugly, but at least you don't repeat yourself. When changing the types of the variant, you only have to update
VAR_HOLDER_VARIANT_TYPE_LIST. The rest stays the same.