2
votes

I want to use new C++11 feature 'extern template class' with STL-container of movable objects (NOT copyable) and get compiler errors.

Example: MyFile.hpp

#pragma once

#include <cstdio>

class MyFile
{
    std::FILE * handle;

public:
    MyFile(const char * filename);

    ~MyFile();

    MyFile(MyFile && that);

    MyFile & operator=(MyFile && that);

    MyFile(const MyFile&) = delete;
    void operator=(const MyFile&) = delete;

    std::FILE const * getFile() const;
};

MyFile.cpp:

#include "MyFile.hpp"

#include <iostream>

MyFile::MyFile(const char * filename)
    : handle{nullptr}
{
    if (!(handle = fopen(filename, "r")))
        throw std::runtime_error("blah blah blah");
}

MyFile::~MyFile()
{
    std::cout << "File::~File()" << std::endl;
    if (handle)
        fclose(handle);
}

MyFile::MyFile(MyFile && that)
    : handle{nullptr}
{
    *this = std::move(that);
}

MyFile & MyFile::operator =(MyFile && that)
{
    std::swap(handle, that.handle);
    return *this;
}

const std::FILE * MyFile::getFile() const
{
    return handle;
}

FileDeque.hpp:

#pragma once

#include <deque>

#include "MyFile.hpp"

extern template class std::deque<MyFile>;
using FileDeque = std::deque<MyFile>;

FileDeque.cpp:

#include "FileDeque.hpp"

template class std::deque<MyFile>;

And the test program: #include

using namespace std;

#include "MyFile.hpp"
#include "FileDeque.hpp"

int main()
{
    cout << "Hello World!" << endl;

    {
        FileDeque files;
        files.emplace_back("C:/eula.1028.txt");
        files.emplace_back("C:/eula.1031.txt");
        files.emplace_back("C:/eula.2052.txt");
    }

    return 0;
}

With Visual Studio 2013 I get the following error:

D:\WinPrograms\Microsoft Visual Studio 12.0\VC\INCLUDE\deque(1714) : error C2280: 'MyFile::MyFile(const MyFile &)' : attempting to reference a deleted function

d:\devel\unique_ptr3\MyFile.hpp(18) : see declaration of 'MyFile::MyFile'

D:\WinPrograms\Microsoft Visual Studio 12.0\VC\INCLUDE\deque(1682) : while compiling class template member function 'void std::deque>::_Insert_n(std::_Deque_const_iterator>>,unsigned int,const MyFile &)' with [ _Ty=MyFile ]

D:\WinPrograms\Microsoft Visual Studio 12.0\VC\INCLUDE\deque(1510) : see reference to function template instantiation 'void std::deque>::_Insert_n(std::_Deque_const_iterator>>,unsigned int,const MyFile &)' being compiled with [ _Ty=MyFile ]

d:\devel\unique_ptr3\FileDeque.hpp(7) : see reference to class template instantiation 'std::deque>' being compiled with [ _Ty=MyFile ]

Generating Code...

It is clear that compiler tries to instantiate std::deque>::_Insert_n function that uses copying of objects, but why?

If std::deque is used directly in main.cpp I get no errrors:

#include <iostream>

#include <deque>

using namespace std;

#include "MyFile.hpp"

using FileDeque = std::deque<MyFile>;

int main()
{
    cout << "Hello World!" << endl;

    {
        FileDeque files;
        files.emplace_back("C:/eula.1028.txt");
        files.emplace_back("C:/eula.1031.txt");
        files.emplace_back("C:/eula.2052.txt");
    }

    return 0;
}

Also tried with clang and gcc and get similiar errrors.

So my questions:

  1. Is it possible to make compiler not to instantiate container's class of movable objects? Why compiler tryies to instantiate methods that require copying support?
  2. Do I want something wrong?
1
It is not a problem of extern templates, it is a problem of an explicit instantiation. Try to add the string template class std::deque<MyFile>; to your second variant of main function. In fact the internal function _Insert_n uses a copy constructor, I think. It does not appear in the second case because implicit instantiation does not instantiate all class template members (only those which are really used).Constructor
Explicit instantiation of a class template instantiates all the members, well-formed or not. You will have to explicitly instantiate only the members that are well-formed for your parameters.Casey
@Constructor: This is the issue, i.e. I don't want that std::deque<MyFile> is instantiated many times in different translation units.gshep
@Casey Is it requirement of the standard or the implementation's bug? Could you provide an extract from the standard.gshep
It is not a bug. See paragraph [temp.explicit] 14.7.2/8 and 14.7.2/9 for details.Constructor

1 Answers

6
votes

C++11 [temp.explicit]/8 states:

An explicit instantiation that names a class template specialization is also an explicit instantiation of the same kind (declaration or definition) of each of its members (not including members inherited from base classes) that has not been previously explicitly specialized in the translation unit containing the explicit instantiation, except as described below.

Since some of the members of std::deque<foo> require a copyable type foo - at the very least, the copy constructor - instantiating them is ill-formed. This is the cause of the errors you observe.

The workaround for this is to explicitly instantiate only the well-formed members that your program uses, something like:

// in FileDeque.hpp:
// Uncomment this to get linker errors suggesting
// other members to explicitly instantiate:
// extern template class std::deque<MyFile>;
extern template std::deque<MyFile>::deque();
extern template std::deque<MyFile>::~deque();
extern template auto std::deque<MyFile>::begin() -> iterator;
extern template auto std::deque<MyFile>::end() -> iterator;
// ...

// in FileDeque.cpp:
template std::deque<MyFile>::deque();
template std::deque<MyFile>::~deque();
template auto std::deque<MyFile>::begin() -> iterator;
template auto std::deque<MyFile>::end() -> iterator;
// ...