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:
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?
Do I want something wrong?
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;
// ...
Related
I am trying to convert boost::object_pool usage on my old project to new visual studio 2019 project, I am using boost version 1.56
ObjectPool.h
class BOOST_OBJECT_POOL_CHECKER
{
boost::object_pool< T > m_sObjectPool;
template <class Arg1>
T* contruct(Arg1& sArg1)
{
T* temp = m_sObjectPool.construct(sArg1);
return temp;
}
}
MaterialServer.h
class MaterialServer
{
MaterialServer(dword serviceType, std::string path);
Material* NEW_MATERIAL();
}
Material.h
class Material
{
BOOST_OBJECT_POOL_CHECKER<Material> m_poolMATERIAL;
Material(MaterialServer* pMatServer);
}
Material.cpp
Material* MaterialServer::NEW_MATERIAL()
{
//Material* returnMaterial = m_poolMATERIAL.construct(this); << error on vs2019, not correct parameter
Material* returnMaterial = m_poolMATERIAL.construct(*this);
}
got first error
boost_1_56\boost\pool\detail\pool_construct_simple.ipp(19,1): error C2664: 'Material::Material(MaterialServer*)': cannot convert argument 1 from 'const T0' to 'MaterialServer *'
ObjectPool.h(68): message : see reference to function template instantiation 'Material *boost::object_pool<T,boost::default_user_allocator_new_delete>::construct<Arg1>(const T0 &)' being compiled
with
[
T=Material,
Arg1=MaterialServer,
T0=MaterialServer
]
should I need upgrade boost version? because previously this code compiled fine on vs2008, but not compiled on vs2019, this c++11 standard so confusing for me
can I get explanation this behavior?
Frankly, this code cannot have compiled under any compiler.
Note: I'm ignoring numerous typos, omitted semi-colons, omitted template declarators, typedefs and access specifiers to focus on the real issues.
You're passing *this which is Material&. However, the contruct [sic] function takes a MaterialServer*.
So, in fact, the commented line was closer, and makes sense IFF it were a member of MaterialServer, not Material.
It would make a lot more sense, logically, for the material server to "create new materials", anyways, and almost works:
class Material {
public:
Material(MaterialServer* pMatServer);
};
class MaterialServer {
BOOST_OBJECT_POOL_CHECKER<Material> m_poolMATERIAL;
public:
MaterialServer(dword serviceType, std::string path);
Material* NEW_MATERIAL();
};
Material* MaterialServer::NEW_MATERIAL()
{
Material* returnMaterial = m_poolMATERIAL.construct(this);
return returnMaterial;
}
I say /almost/ because construct takes its argument by mutable reference. That won't compile here (this is NOT a mutable lvalue).
So, fixing that:
template <typename Arg1> T* construct(Arg1 sArg1) {
return m_sObjectPool.construct(sArg1);
}
Or, more generically:
template <typename... Arg> T* construct(Arg&&... sArg) {
return m_sObjectPool.construct(std::forward<Arg>(sArg)...);
}
We get "compiling code". We can't link it (the constructors aren't defined).
Adding some more imagined code:
Live On Coliru
#include <boost/pool/object_pool.hpp>
#include <iomanip>
#include <iostream>
#include <string>
#include <atomic>
using dword = uint32_t;
template <typename T> class BOOST_OBJECT_POOL_CHECKER {
boost::object_pool<T> m_sObjectPool;
public:
template <typename... Arg> T* construct(Arg&&... sArg)
{
return m_sObjectPool.construct(std::forward<Arg>(sArg)...);
}
};
class MaterialServer; // forward declare
class Material {
public:
Material(MaterialServer* pMatServer);
};
class MaterialServer {
BOOST_OBJECT_POOL_CHECKER<Material> m_poolMATERIAL;
dword _serviceType;
std::string _path;
public:
MaterialServer(dword serviceType, std::string path)
: _serviceType(serviceType)
, _path(path)
{
}
Material* NEW_MATERIAL();
dword getServiceType() const { return _serviceType; }
std::string_view getPath() const { return _path; }
};
Material* MaterialServer::NEW_MATERIAL()
{
Material* returnMaterial = m_poolMATERIAL.construct(this);
return returnMaterial;
}
Material::Material(MaterialServer* pMatServer)
{
static std::atomic_int id{0};
std::cout << "Material " << id++ << " from server ("
<< pMatServer->getServiceType() << ", "
<< std::quoted(pMatServer->getPath()) << ")\n";
}
int main() {
MaterialServer a(123, "Material/a/resource");
MaterialServer b(234, "Material/b/resource");
a.NEW_MATERIAL();
a.NEW_MATERIAL();
b.NEW_MATERIAL();
a.NEW_MATERIAL();
}
Prints
Material 0 from server (123, "Material/a/resource")
Material 1 from server (123, "Material/a/resource")
Material 2 from server (234, "Material/b/resource")
Material 3 from server (123, "Material/a/resource")
There is a nice little technique here to allow the use of std::unique_ptr with incomplete types.
Here is the relevant code:
// File: erasedptr.h
#include <memory>
#include <functional>
// type erased deletor (an implementation type using "veneer")
template <typename T>
struct ErasedDeleter : std::function<void(T*)>
{
ErasedDeleter()
: std::function<void(T*)>( [](T * p) {delete p;} )
{}
};
// A unique_ptr typedef
template <typename T>
using ErasedPtr = std::unique_ptr<T, ErasedDeleter<T>>;
// Declare stuff with an incomplete type
struct Foo;
ErasedPtr<Foo> makeFoo();
// File: main.cpp (Foo's definition is not available in this translation unit)
#include "erasedptr.h"
int main() {
ErasedPtr<Foo> f; // [R1]
f = makeFoo();
// ~Foo() gets called fine
}
// File: foo.cpp
#include <iostream>
#include "erasedptr.h"
struct Foo {
~Foo() { std::cout << "~Foo()\n" ; }
};
ErasedPtr<Foo> makeFoo() { return ErasedPtr<Foo>(new Foo); }
This works on all compilers I tried: gcc 4.9, clang 3.5, and msvc VS13 and VS15. But they all generate the following warning:
deletion of pointer to incomplete type 'Foo'; no destructor called
If [R1] above is replaced with ErasedPtr<Foo> f( makeFoo() );, the warning does not manifest.
At the end, the destructor does get called and there doesn't seem to be an actual problem. The warning is problematic, because it can not be ignored in quality-critical environments, and this otherwise very useful pattern is unavailable.
To reproduce, create the 3 files erasedptr.hpp, main.cpp, foo.cpp as above and compile.
So the question is: what is going on? Could there be any alternative implementation to circumvent this warning?
Your question is valid but the code you are trying to use is a bit convoluted, so let me haul off to get to the wanted solution.
What you do here isn't type erasure (and Andrzej is wrong about this, too) - you just capture the deletion into a runtime function value, without any benefit BTW. Type erasure OTH is when other code parts lose information about the initial type.
I tried your code on VS 2015, which also calls the destructor ~Foo(), however this is only big luck, which means the compiler is doing some fancy stuff. If you don't use std::function but write your own custom deleter then the destructor isn't called.
Solution 1 - type erasure
If you really would like to erase the type you would write/use ErasedPtr the following way:
erasedptr.h
// file erasedptr.h
#include <memory>
#include <functional>
// make type erased deleter
template <typename T>
std::function<void(void*)> makeErasedDeleter()
{
return {
[](void* p) {
delete static_cast<T*>(p);
}
};
};
// A unique_ptr typedef
template <typename T>
using ErasedPtr = std::unique_ptr<T, std::function<void(void*)>>;
foo.cpp
// file foo.cpp
#include <iostream>
#include "erasedptr.h."
struct Foo {
~Foo() { std::cout << "~Foo()\n" ; }
};
// capture creation and deletion of Foo in this translation unit
ErasedPtr<Foo> makeFoo() {
return { new Foo, makeErasedDeleter<Foo>() };
}
main.cpp
// file main.cpp (Foo's definition is not available in this translation unit)
#include "erasedptr.h"
// fwd decl Foo
struct Foo;
ErasedPtr<Foo> makeFoo();
int main() {
ErasedPtr<Foo> f; // [R1]
f = makeFoo();
// ~Foo() gets called fine
}
This way only foo.cpp needs to know about the actual type and it captures the deletion into std::function.
Solution 2 - incomplete types, really
What you actually really want is to deal with incomplete types. The 'issue' you have with STL's default deleter std::default_delete is that it asserts at compile time whether deletion is safe - and it is damn right about it!
To make it work correctly is to tell the compiler/linker you actually cared about a correct implementation of the deletion, using explicit template instantiation. This way you don't need any special typedef/template alias for your unique pointer:
foo.cpp
// file foo.cpp
#include "foo_fwddecl.h"
// capture creation of Foo in this translation unit
std::unique_ptr<Foo> makeFoo() {
return std::make_unique<Foo>();
}
// explicitly instantiate deletion of Foo in this translation unit
template void std::default_delete<Foo>::operator()(Foo*) const noexcept;
template void std::default_delete<const Foo>::operator()(const Foo*) const noexcept;
// note: possibly instantiate for volatile/const volatile modifiers
foo_fwddecl.h
#include <memory>
struct Foo;
std::unique_ptr<Foo> makeFoo();
extern template void std::default_delete<Foo>::operator()(Foo*) const noexcept;
extern template void std::default_delete<const Foo>::operator()(const Foo*) const noexcept;
// note: possibly instantiate for volatile/const volatile modifiers
main.cpp
// file main.cpp (Foo's definition is not available in this translation unit)
#include "foo_fwddecl.h"
int main() {
std::unique_ptr<Foo> f; // [R1]
f = makeFoo();
// ~Foo() gets called fine
}
I have a C++/Win32/MFC project in Visual Studio 2008, and I'm getting a strange error message when I compile it.
I've created a small project to demonstrate the problem, and the main code is
#ifndef _MyObject_h
#define _MyObject_h
class MyObject
{
public:
MyObject()
{
}
};
#endif // _MyObject_h
// --- END MyObject.h
// --- BEGIN ObjectData.h
#ifndef _ObjectData_h
#define _ObjectData_h
template <typename DataPolicy>
class ObjectData
{
public:
DataPolicy *data;
ObjectData() :
data(NULL)
{
}
ObjectData(const ObjectData<DataPolicy> ©) :
data(copy.data)
{
}
ObjectData<DataPolicy> & operator=(const ObjectData<DataPolicy> ©)
{
this->data = copy.data;
return *this;
}
};
#endif // _ObjectData_h
// --- END ObjectData.h
// --- BEGIN Tool.h
#ifndef _Tool_h
#define _Tool_h
#include "ObjectData.h"
template <typename ObjectPolicy>
class Tool
{
private:
ObjectData<typename ObjectPolicy> _object;
public:
Tool(ObjectData<typename ObjectPolicy> obj);
};
#endif // _Tool_h
// --- END Tool.h
// --- BEGIN Tool.cpp
#include "stdafx.h"
#include "Tool.h"
template <typename ObjectPolicy>
Tool<ObjectPolicy>::Tool(ObjectData<typename ObjectPolicy> obj) :
_object(obj)
{
}
// --- END Tool.cpp
// --- BEGIN Engine.h
#ifndef _Engine_h
#define _Engine_h
#include "Tool.h"
#include "MyObject.h"
class Engine
{
private:
MyObject *_obj;
public:
Engine();
~Engine();
void DoSomething();
};
#endif // _Engine_h
// --- END Engine.h
// --- BEGIN Engine.cpp
#include "stdafx.h"
#include "Engine.h"
Engine::Engine()
{
this->_obj = new MyObject();
}
Engine::~Engine()
{
delete this->_obj;
}
void Engine::DoSomething()
{
ObjectData<MyObject> objData;
objData.data = this->_obj;
// NEXT LINE IS WHERE THE ERROR OCCURS
Tool< ObjectData<MyObject> > *tool = new Tool< ObjectData<MyObject> >(objData);
}
// --- END Engine.cpp
Errors:
Engine.cpp
c:\projects\myproject\myproject\engine.cpp(18) : error C2664: 'Tool::Tool(ObjectData)' : cannot convert parameter 1 from 'ObjectData' to 'ObjectData'
with
[
ObjectPolicy=ObjectData,
DataPolicy=ObjectData
]
and
[
DataPolicy=MyObject
]
and
[
DataPolicy=ObjectData
]
No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called
1>Build log was saved at "file://c:\Projects\MyProject\MyProject\Debug\BuildLog.htm"
MyProject - 1 error(s), 0 warning(s)
Thanks for any help.
There are a few problems with your code. First of all, you are using the typename keyword in a wrong way. typename can be used only when qualified type names are used (and is required when the type names are dependent), which is not your case:
template <typename ObjectPolicy>
class Tool
{
private:
ObjectData<typename ObjectPolicy> _object; // "typename" is not needed!
public:
Tool(ObjectData<typename ObjectPolicy> obj); // "typename" is not needed!
};
The problem you complain about, however, is in your instantiation of the Tool class template:
Tool< ObjectData<MyObject> > *tool = new Tool< ObjectData<MyObject> >(objData);
Your Tool<> template contains a member variable of type ObjectData<ObjectPolicy>, where ObjectPolicy is the class template parameter. However, in the line above you instantiate Tool with ObjectData<MyObject> as a parameter. This means your member variable will have type ObjectData<ObjectData<MyObject>>, and this will also be the type of the constructor's parameter.
Because of this, you are trying to invoke a constructor which accepts an ObjectData<ObjectData<MyObject>> with an argument of a mismatching type ObjectData<MyObject>. Hence, the error you get.
You should change your instantiation into:
Tool< MyObject > *tool = new Tool< MyObject >(objData);
Another problem is that you have the definition of Tool's member functions in a separate .cpp files. You should not do that: the linker won't be able to see it when processing a separate translation unit.
To solve this problem, put the definitions of your class template's member functions into the same header where the class template is defined (Tool.h in your case).
Tool< ObjectData<MyObject> > *tool = new Tool< ObjectData<MyObject> >(objData);
template <typename ObjectPolicy>
Tool<ObjectPolicy>::Tool(ObjectData<typename ObjectPolicy> obj) :
_object(obj)
{
}
It seems to me you might not really understand how templates work.
Check out the following C++ Templates
What you currently have is invalid C++ syntax. Take a look and give it another shot.
Dear all, I've been stuck with this problem now for a few days and my searches were not successful.
What I am trying to do:
I want a template reader class (VariableReader) to handle different types of variables (usually unsigned int and pointers to vector).
I started with
#ifndef READER_H_
#define READER_H_
#include <string>
namespace BAT {
template <typename variableType = unsigned int>
class VariableReader {
public:
VariableReader<variableType>();
VariableReader<variableType>(std::string varName);
virtual ~VariableReader<variableType>();
std::string getVariableName();
void setVariableName(std::string varName);
bool isValidVariableName(std::string varName);
variableType getVariable();
private:
std::string variableName;
variableType variable;
};
}
#endif
and
#include "../../interface/Readers/VariableReader.h"
namespace BAT {
template<typename variableType>
VariableReader<variableType>::VariableReader() :
variableName("") {
// TODO Auto-generated constructor stub
}
template <typename variableType>
VariableReader<variableType>::VariableReader(std::string varName) :
variableName(varName) {
}
template <typename variableType>
std::string VariableReader<variableType>::getVariableName() {
return variableName;
}
template <typename variableType>
void VariableReader<variableType>::setVariableName(std::string varName) {
if (VariableReader::isValidVariableName(varName)) {
variableName = varName;
}
}
template <typename variableType>
bool VariableReader<variableType>::isValidVariableName(std::string varName) {
return varName != "";
}
template <typename variableType>
VariableReader<variableType>::~VariableReader() {
// TODO Auto-generated destructor stub
}
}
However, although it seems to compile I can't use it within other projects.
EDIT: forgot to post test-code:
#include "cute.h"
#include "ide_listener.h"
#include "cute_runner.h"
#include "Readers/VariableReader.h"
using namespace BAT;
static VariableReader<int> *reader;
void setUp(){
reader = new VariableReader<int>::VariableReader();//this is problem-line
}
void thisIsATest() {
ASSERTM("start writing tests", false);
}
void runSuite(){
cute::suite s;
//TODO add your test here
s.push_back(CUTE(thisIsATest));
cute::ide_listener lis;
cute::makeRunner(lis)(s, "The Suite");
}
int main(){
runSuite();
}
I get following error message:
Building target: BAT_Tests
Invoking: GCC C++ Linker
g++ -L"/workspace/BAT/Debug Gcov" -fprofile-arcs -ftest-coverage -std=c99 -o"BAT_Tests" ./src/Test.o -lBAT
./src/Test.o: In function `setUp()':
/workspace/BAT_Tests/Debug Gcov/../src/Test.cpp:13: undefined reference to `BAT::VariableReader<int>::VariableReader()'
collect2: ld returned 1 exit status
make: *** [BAT_Tests] Error 1
As I understand it the linker tries to find the constructor for VariableReader, which is not explicitly defined since I want to have a general constructor only.
Please help me to understand what I am missing.
The C++ FAQ Lite section on How can I avoid linker errors with my template functions? shows two solutions:
Move the template class's methods into the .h file (or a file included by the .h file).
Instantiate the template in the .cpp file using template VariableReader<unsigned int>;.
The constructor(s) and destructor doesn't need the template arguments in it. In addition, template classes must have the full source available to compile- you can't declare the members and define them in another translation unit like you can with normal classes.
Why does the following give no compilation error?:
// T.h
template<class T> class X
{
public:
void foo(int a = 42);
};
// Main.cpp
#include "T.h"
#include <iostream>
template<class T> void X<T>::foo(int a = 13)
{
std::cout << a << std::endl;
}
int main()
{
X<int> x;
x.foo(); // prints 42
}
It seems as though the 13 is just silently ignored by the compiler. Why is this?
The cooky thing is that if the class template definition is in Main.cpp instead of a header file, I do indeed get the default parameter redefinition error.
Now I know the compiler will complain about this if it were just an ordinary (non-template) function.
What does the standard have to say about default parameters in class template member functions or function templates?
8.3.6 ยง6 The default arguments in a member function definition that
appears outside of the class
definition are added to the set of
default arguments provided by the
member function declaration in the
class definition.
[Example:
class C {
void f(int i = 3);
void g(int i, int j = 99);
};
void C::f(int i = 3) // error: default argument already
{ } // specified in class scope
void C::g(int i = 88, int j) // in this translation unit,
{ } // C::g can be called with no argument
--end example]
According to the standard, it should give you an error.