Visual studio won't compile template class wit

2019-07-14 05:28发布

问题:

I'm following a book about SFML game development, but I'm stuck on the second chapter, because I can't compile the code I just wrote.

It's almost word-by-word copy from the book (except from member variable name and exception text). I have experience with C++ and templates, but I have never seen this error before and I've been staring at this for few hours now and I don't see anything wrong with this code.

Here is my *.h file:

#pragma once
#include <map>
#include <memory>
#include <string>
#include <stdexcept>
#include <cassert>
#include "enumFile.h"

template <typename Resource, typename Identifier>
class ResourceHolder
{
public:
    ResourceHolder(void);
    ~ResourceHolder(void);

    void load(Identifier id, const std::string & filename);

    template <typename Parameter>
    void load(Identifier id, const std::string & filename, 
              const Parameter& secondParam);

    Resource & get(Identifier id);
    const Resource & get(Identifier id) const;

private:
    std::map<Identifier, std::unique_ptr<Resource>> resourceMap;
};

#include "ResourceHolder.inl"

and here is my *.inl file:

template <typename Resource, typename Identifier>
void ResourceHolder<Resource, Identifier>::load(Identifier id, const std::string& filename)
{
    // Create and load resource
    std::unique_ptr<Resource> resource(new Resource());
    if (!resource->loadFromFile(filename))
        throw std::runtime_error("Failed to load resource: " + filename);

    // If loading successful, insert resource to map
    auto inserted = resourceMap.insert(std::make_pair(id, std::move(resource)));
    assert(inserted.second);
}

template <typename Resource, typename Identifier>
template <typename Parameter>
void ResourceHolder<Resource, Identifier>::load(Identifier id, const std::string& filename, 
                                                const Parameter& secondParam)
{
    // Create and load resource
    std::unique_ptr<Resource> resource(new Resource());
    if (!resource->loadFromFile(filename, secondParam))
        throw std::runtime_error("Failed to load resource: " + filename);

    // If loading successful, insert resource to map
    auto inserted = resourceMap.insert(std::make_pair(id, std::move(resource)));
    assert(inserted.second);
}

template <typename Resource, typename Identifier>
Resource& ResourceHolder<Resource, Identifier>::get(Identifier id)
{
    auto found = resourceMap.find(id);
    assert(found != resourceMap.end());

    return *found->second;
}

template <typename Resource, typename Identifier>
const Resource& ResourceHolder<Resource, Identifier>::get(Identifier id) const
{
    auto found = resourceMap.find(id);
    assert(found != resourceMap.end());

    return *found->second;
}

Sorry for a lot of code, but I'm desperate here. I'm getting unusual errors, all in the *.inl file, instead of writing them, I took a screenshot:

Any idea how to fix this?

EDIT: a word on how the class is used.

I have and enum inside a texture namespace (that is what "enumFile.h" is)

namespace Textures
{
    enum ID {Landscape, Airplane, Missile};
}

When I want to use the class, I use it as follows:

ResourceHolder<sf::Texture, Textures::ID> textureHolder;
textureHolder.load(Textures::Airplane, "path/to/texture.png");

回答1:

Removing or excluding the ResourceHolder.inl file from the project in VS and then adding it again solved the problem for me. I have no idea as to why this worked, but now it compiles fine.



回答2:

This problem is caused by 'File Type' property of your inl file. Visual Studio use this property to determine how to handle a file while building a project. For example, 'C++ Header File'(.h/hpp) will not be compiled by itself(code block in header file is exceptional), but 'C/C++ Code'(.c/cpp) files are does.

If you added a C/CPP file at first and changed its file extension to inl, 'File Type' of inl file must be remaining as 'C/C++ Code' in VC project. But inl file is not a compilable code without template arguments. So it's file type should not be 'C/C++ Code'. Instead, set it to 'C++ Header File' or 'Document'.

When you add files into VC++ project, VS automatically set 'File Type' property according to their extension. And VS sets 'Document' file type for inl files. This is why removing and re-adding that inl file solved your problem.

Note that compiling templated code is actually performed when compiler process some codes that use it, like a header file. For example, if you implemented a template class in a static library project and it's not used in your library project, that class will not be compiled when you build that lib project. Instead, when you build a project that use lib file and the template class with template arguments, it will be compiled. This is why you should implement member functions of template class or templated functions in header file or something else like inl which is included in header file.



回答3:

I know this is an old topic, but I was having the same issue until a few moments ago (I'm reading the same SFML book, by the way), and I've just found a quick and simple solution, so I wish to share it for future reference.

At the "Solution Explorer" tab on your Visual Studio, find your .inl file and RIGHT CLICK it. Then, select the option "Exclude from project". Done!



回答4:

The above code compile on msvc2012. I just removed #include "enumFile.h and added the ctor and dctor of ResourceHolder. this is my main.

update

#include "resourceholder.h"

struct Resource
{
    bool loadFromFile( const std::string& filename )
    {
        return true;
    }
};

int main()
{
    Resource r;
    ResourceHolder< Resource, int > rh;
    rh.load( 30, "" );
}

Here the problem is that Identifier cannot be an enum. Identifier must be a struct or class. You can wirte something

namespace Texture { struct a_tag{ int id }; struct b_tag{ int id }; }

Or use little metaprogramming:

/** Helper class to use integer in template parameters. */
template < Texture::ID >
struct EnumToStruct {
    static const Texture::ID value = e;
};

int main()
{
    Resource r;
    ResourceHolder< Resource, EnumToStruct<Texture::Car> > rh;
    rh.load( 30, "" );
}