Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Marking function as virtual causes compiler error with unique_ptr

I have a templated class that wraps a vector. I'm trying to store unique_ptrs in this class, and it works fine. However, when I mark the void add(const T& elem) function as virtual, my compiler (clang) tells me that I'm making a "call to implicitly-deleted copy constructor" for unique_ptr.

I understand that unique_ptrs can't be copied, so that's why I created the void add(T&& elem) function. I just don't know why marking the other add function as virtual causes the compiler error.

Thanks for your time.

#include <iostream>
#include <vector>
#include <memory>

using namespace std;

template <typename T>
class ContainerWrapper {
private:
    vector<T> vec;
public:
    ContainerWrapper() : vec() {

    }

    //Marking this as virtual causes a compiler error
    void add(const T& elem) {
        vec.push_back(elem);
    }

    void add(T&& elem) {
        vec.push_back(std::move(elem));
    }

    T removeLast() {
        T last = std::move(vec.back());
        vec.pop_back();
        return last;
    }
};

int main() {
    ContainerWrapper<unique_ptr<string>> w;
    w.add(unique_ptr<string>(new string("hello")));

    unique_ptr<string> s = w.removeLast();

    cout << *s << endl;
}
like image 596
Torrential Torrential Avatar asked Jan 28 '13 01:01

Torrential Torrential


2 Answers

This is most likely due to ContainerWrapper being a template. With templates, the compiler most often won't even check member functions as long as you don't call them. However, marking it virtual forces the function to be present (you might also even get a link error).

You can possibly take a look at this post : When the virtual member functions of a template class instantiated?.

like image 198
François Moisan Avatar answered Nov 09 '22 15:11

François Moisan


The problem here is that std::unique_ptr has its copy constructor marked as =delete. This means that the vec.push_back(elem) call inside the add(T const&) overloaded member function will not compile when being called with a std::unique_ptr. The compiler will realize this as soon as that member function is being instantiated.

The Standard has 2 relevant quotes here in 14.7.1 Implicit instantiation [temp.inst]:

6 If the overload resolution process can determine the correct function to call without instantiating a class template definition, it is unspecified whether that instantiation actually takes place.

10 [...] It is unspecified whether or not an implementation implicitly instantiates a virtual member function of a class template if the virtual member function would not otherwise be instantiated. [...]

Clause 6 states that -without the virtual keyword- the compiler is allowed but not required to instantiate both add(T const&) and add(T&&) in order to resolve which overload is the best match. Neither gcc 4.7.2 nor Clang 3.2 need the instantiation because they happen to deduce that rvalue references always are a better match for temporaries than lvalue references.

Clause 10 states that -even with the virtual keyword- the compiler is also allowed but not required to instantiate add(T const&) and add(T&&) in order to resolve which overload is the best match. Both gcc 4.7.2 and Clang 3.2 happen to the instantiate both member functions, even though they both could have deduced that the lvalue overload would never be a better match.

Note that if you make ContainerWrapper a regular class with a nested typedef unique_ptr<string> T;, then both gcc and Clang will generate errors with or without the virtual keyword because they have to generate code for both member functions. This will not be SFINAE'ed away because it the error does not happen during substitution of deduced arguments.

Conclusion: this is not a bug but a quality of implementation issue.

like image 41
TemplateRex Avatar answered Nov 09 '22 14:11

TemplateRex