重载可变参数模板化方法

问题描述

我的编译器很难理解这段代码,我要花几个小时才能找出问题所在。

#include <utility>
#include <string>

template<typename Derived>
struct AssetLoader {
    template<typename... Args>
    void doLoad(Args&& ... args) const {
        static_cast<const Derived *>(this)->load(std::forward<Args>(args)...);
    }
};

struct TextureLoader : public AssetLoader<TextureLoader> {
    void load(const std::string &path) const {
        // some code
    }
};

struct SomeOtherLoader : public AssetLoader<SomeOtherLoader> {
    void load(const std::string &path) const {
        // some code
    }
};

template<typename DefaultLoader>
class Resources {
    AssetLoader<DefaultLoader> m_defaultLoader;

public:
    Resources(AssetLoader<DefaultLoader> defaultLoader):
        m_defaultLoader(std::move(defaultLoader)) {}

    template<typename... Args>
    void load(Args&& ... args) {
        load(m_defaultLoader,std::forward<Args>(args)...);
    }

    template<typename Loader,typename... Args>
    void load(const AssetLoader<Loader>& loader,Args&& ... args) {
        loader.doLoad(std::forward<Args>(args)...);
    }
};

int main() {
    Resources<TextureLoader> resources(TextureLoader{});
    resources.load("image.png");
    resources.load(SomeOtherLoader{},"example.jpg");
    return 0;
}

我收到此错误

Fatal error: template instantiation depth exceeds maximum of 900 (use -ftemplate-depth= to increase the maximum)
         return load(m_defaultLoader,std::forward<Args>(args)...);
                                      ~~~~~~~~~~~~~~~~~~^~~~~~

我的真实代码要复杂得多,但是我将其精简了一下,但是却遇到了同样的错误

如果我评论一个重载,它会很好地工作,但是如果不传递Loader,就无法调用load()方法。我想要认加载程序的重载,所以我可以做resources.load("image.png");

我使用mingw64 8.1

有什么主意吗?

解决方法

编译器告诉您问题的确切出处-无限递归:

template<typename... Args>
void load(Args&& ... args) {
    load(m_defaultLoader,std::forward<Args>(args)...);
}

此函数无限调用自己。永远不会选择其他重载,因为Args&&的匹配比AssetLoader<Loader> const&(特别是TextureLoader const&)更好。

给其他重载一个不同的名称以消除歧义...

template<typename... Args>
void load(Args&& ... args) {
    load2(m_defaultLoader,std::forward<Args>(args)...);
}

template<typename Loader,typename... Args>
void load2(const AssetLoader<Loader>& loader,Args&& ... args) {
    loader.doLoad(std::forward<Args>(args)...);
}
,

挖掘之后,我终于找到了解决方案。基本上,我必须检查Args...的第一种类型本身不是加载器,因此将迫使编译器选择第二种过载。

这是代码

#include <utility>
#include <string>

template<typename T1,typename...>
struct first {
    typedef T1 type;
};

template<typename... T>
using first_t = typename first<T...>::type;

template<typename Derived>
struct AssetLoader {
    template<typename... Args>
    void doLoad(Args&& ... args) const {
        static_cast<const Derived *>(this)->load(std::forward<Args>(args)...);
    }
};

struct TextureLoader : public AssetLoader<TextureLoader> {
    void load(const std::string& path) const {
        // some code
    }
};

struct SomeOtherLoader : public AssetLoader<SomeOtherLoader> {
    void load(const std::string& path) const {
        // some code
    }
};

template<typename DefaultLoader,std::enable_if_t<
        std::is_base_of_v<
            AssetLoader<DefaultLoader>,DefaultLoader
        >,int
    > = 0
>
class Resources {
    DefaultLoader m_defaultLoader;

public:
    Resources(DefaultLoader defaultLoader):
        m_defaultLoader(std::move(defaultLoader)) {}

    template<
        typename... Args,std::enable_if_t<
            sizeof...(Args) == 0 ||
            !std::is_base_of_v<
                AssetLoader<std::decay_t<first_t<Args...>>>,std::decay_t<first_t<Args...>>
            >,int
        > = 0
    >
    void load(Args&& ... args) {
        load(m_defaultLoader,std::forward<Args>(args)...);
    }

    template<typename Loader,typename... Args>
    void load(const AssetLoader<Loader>& loader,Args&& ... args) {
        loader.doLoad(std::forward<Args>(args)...);
    }
};

int main() {
    Resources<TextureLoader> resources(TextureLoader{});
    resources.load("image.png");
    resources.load(SomeOtherLoader{},"example.jpg");
    return 0;
}