1

This started out as a question of: "Why isn't it possible to explicitly instantiate an std::vector of std::unique_ptr?" as in:

template class std::vector<std::unique_ptr<int>>;

although following explicit instantiations and variables are fine:

template class std::vector<int>;
template class std::unique_ptr<int>;

int main() {
    std::vector<int> ints;
    std::vector<std::unique_ptr<int>> pointers;
}

But the question changed to: "What would be the alternatives?"

I'll post my though walk-through to answer both, since I didn't find a question that felt similar enough. I am also looking for alternatives, if any other.

iwat0qs
  • 146
  • 3
  • 10

1 Answers1

1

Why isn't it possible?

It's not possible to do this:

template class std::vector<std::unique_ptr<int>>; // (1)

Although the compiler is totally fine with such a variable:

std::vector<std::unique_ptr<int>> vec; // (2)

As I get it, (2) is possible, because the methods using copy assignments/constructors are never implicitly instantiated in vector;
and (1) is not possible, because the vector tries to instantiate methods trying to do copies on unique_ptr.

The first method not to compile for gcc 7.2.1 c++17, for example, is vector::push_back(const T&);

Because T = unique_ptr, and unique_ptr obviously doesn't support copy operations.

What are the alternatives?

shared_ptr instead of unique_ptr works, because it supports copy assignments, while also looking clean. But it has some overhead, as I've heard, and there are no intentions to share the resource ownership.

I also imagine writing a wrapper, that defines "copy" operations, which would actually move on copy or even throw, such as:

template<typename T>
struct UniquePtrWithCopy {
    /*mutable if move is used*/ std::unique_ptr<T> mPtr;

    UniquePtrWithCopy() = default;

    explicit UniquePtrWithCopy(std::unique_ptr<T>&& other)
            : mPtr{std::move(other)} {
    }

    UniquePtrWithCopy(const UniquePtrWithCopy& other) {
        mPtr = std::move(other.mPtr); // needed for operations, like resize
        // or
        throw std::runtime_error{"This is not intended"};
    }

    UniquePtrWithCopy& operator =(const UniquePtrWithCopy& other) {
        if(this != &other) {
            mPtr = std::move(other.mPtr);
        }
        return *this;
        // or
        throw std::runtime_error{"This is not intended"};
    }
};

And then this is possible:

template class std::vector<UniquePtrWithMovingCopy<int>>;

So I guess, while I tried to find an answer, I've found it out by myself after all, but I'd be happy to hear some other ways or fixes.

It would've been nice, though, if the compiler did some sort of sfinae trick and only partially instantiated whatever is possible, but that probably has some problems of it's own.

iwat0qs
  • 146
  • 3
  • 10