I have a template class A<T>
with a nested class Inner
- I use it to hide implementation details. I might have different instantiations of the class A
. A
can hold a pointer to Inner
and I might need to do operations with those pointers within 2 different instantiations. In those cases, I might get 2 different Inner classes - A<T1>::Inner
and A<T2>::Inner
. From a compiler perspective, these are different types and are not compatible. A non-working example:
template<typename T>
class A {
template <typename T2> friend class A;
struct Inner {
Inner(int64_t timestamp) : timestamp(timestamp) {}
int64_t timestamp;
};
Inner* inner_pointer;
T val;
public:
template<typename U>
A(const A<U> other, const T& val) : inner_pointer(other.inner_pointer), val(val) {}
A(const T& val, int64_t time_now) : inner_pointer(new Inner(time_now)), val(val) {}
};
int main(int argc, char const *argv[]){
A first('a', 123);
A second(first, 0.2);
return 0;
}
One way to get around this could be to use reinterpret_cast<Inner*>(other.inner_pointer)
, but that's ugly, and I would guess a UB as well.
Another way is not to make Inner
and nested class, but a separate class instead:
struct Inner {
Inner(int64_t timestamp) : timestamp(timestamp) {}
int64_t timestamp;
};
template<typename T>
class A {
template <typename T2> friend class A;
Inner* inner_pointer;
T val;
public:
template<typename U>
A(const A<U> other, const T& val) : inner_pointer(other.inner_pointer), val(val) {}
A(const T& val, int64_t time_now) : inner_pointer(new Inner(time_now)), val(val) {}
};
This solves the problem, but then it makes the Inner
class visible to anyone who uses the A
class since it is part of the header file that needs to be included and it also pollutes the scope.
I guess the STL implementations get around this using names that are reserved (starting with underscore). Is there a simple/elegant way how to achieve this without exposing the Inner
class?
Here's a trick based on the assumption that A<void>
is not a valid instantiation. (If this is an artifact of simplification, there are other ways to get a similar effect.)
First, declare (not define) your template and define A<void>
with Inner
as a nested type.
More generally, you could move all the common functionality, such as the inner_pointer
member, to this specialization. This opens the possibility of moving the common code, including the definition of Inner
, out of the header to a source file.
#include <cstdint>
// Declare the template.
template<typename T>
class A;
// Define A<void>
template<>
class A<void> {
template <typename T2> friend class A;
struct Inner {
Inner(int64_t timestamp) : timestamp(timestamp) {}
int64_t timestamp;
};
// Private constructor prevents the use outside the A template.
A() = default;
// Protected (or private) destructor allows safe polymorphism without virtual functions.
~A() = default;
};
Next, define your general template, using A<void>
as a base type.
template<typename T>
class A : private A<void> {
template <typename T2> friend class A;
Inner* inner_pointer;
T val;
public:
template<typename U>
A(const A<U> other, const T& val) : inner_pointer(other.inner_pointer), val(val) {}
A(const T& val, int64_t time_now) : inner_pointer(new Inner(time_now)), val(val) {}
};
All your template instantiations now use the same Inner
type.
The drawback of this is that we have not really changed the problem; the A<void>
type is visible to anyone who uses the A
template. However, it is not usable (since everything is private), and we have eliminated the namespace pollution as there is not a new identifier introduced.