Nested class of a template class - interoperability and visibility

94 Views Asked by At

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?

1

There are 1 best solutions below

0
JaMiT On BEST ANSWER

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.