停止不断增加的无限递归模板实例化,不需要

问题描述 投票:0回答:1

我正在实现一个图类,每个顶点具有不一定相同类型的Label。我希望用户能够提供任何标签(在编译时),而图形或顶点不知道类型是什么。为此,我使用了模板化多态性(已隐藏在Label类中),以使Labels具有值语义。它就像一个咒语,相关代码如下(暂时忽略注释部分):

//Label.hpp:
#include <memory>

class Label {
public:
    template<class T> Label(const T& name) : m_pName(new Name<T>(name)) {}
    Label(const Label& other) : m_pName(other.m_pName->copy()) {}
//  Label(const Label& other, size_t extraInfo) : m_pName(other.m_pName->copyAndAddInfo(extraInfo)) {}
    bool operator==(const Label& other) const { return *m_pName == *other.m_pName; }
private:
    struct NameBase {
    public:
        virtual ~NameBase() = default;
        virtual NameBase* copy() const = 0;
//      virtual NameBase* copyAndAddInfo(size_t info) const = 0;
        virtual bool operator==(const NameBase&  other) const = 0;
    };

    template<class T> struct Name : NameBase {
    public:
        Name(T name) : m_name(std::move(name)) {}
        NameBase* copy() const override { return new Name<T>(m_name); }
//      NameBase* copyAndAddInfo(size_t info) const override {
//          return new Name<std::pair<T, size_t>>(std::make_pair(m_name, info));
//      }
        bool operator==(const NameBase& other) const override {
            const auto pOtherCasted = dynamic_cast<const Name<T>*>(&other);
            if(pOtherCasted == nullptr) return false;
            return m_name == pOtherCasted->m_name;
        }
    private:
        T m_name;
    };

    std::unique_ptr<NameBase> m_pName;
};

用户(又名我的一个要求是,能够创建图的不相交并集(他已经能够创建双图,图的并集(其中具有相同Label的顶点被映射到相同的顶点)),等等。)。希望新图的标签是旧标签和一些整数的对,表示标签来自哪个图(这也确保了新标签都不同)。为此,我认为我可以使用Label类的注释部分,但是我的g ++ 17编译器存在的问题是,当我定义第一个类型为T的Label时,它试图实例化所有可以被使用:

Name<T>, Name<std::pair<T, size_t>>, Name<std::pair<std::pair<T, size_t>, size_t>>, ...

例如,尝试编译它(只是一个例子,否则可以工作):

// testLabel.cpp:
#include "Label.hpp"
#include <vector>
#include <iostream>

int main() {
    std::vector<Label> labels;
    labels.emplace_back(5);
    labels.emplace_back(2.1);
    labels.emplace_back(std::make_pair(true, 2));
    Label testLabel(std::make_pair(true, 2));
    for(const auto& label : labels)
        std::cout<<(label == testLabel)<<std::endl;
    return 0;
}

编译只是冻结。 (我没有看到别人看到的消息“超出了最大模板递归容量”,但显然它试图实例化所有内容)。我试图将函数分离到另一个类中,并仅显式初始化所需的模板,以欺骗编译器,但无效。

所需的行为(我不知道是否可能)是实例化使用的模板类(连同成员函数声明),但是懒惰地定义成员函数,即仅在它们真正被调用时。例如,如果我调用Label(3),应该有一个类Name<int>,但是函数

NameBase* Name<int>::copyAndAddInfo(size_t info) const;

仅在我调用时定义。 (因此,Name<std::pair<int, size_t>>仅将按需实例化)

感觉上应该可行,因为编译器已经按需定义了模板化函数。

应该完全改变实现并使用变体的想法,但是

  1. 我不想跟踪用户手动需要的类型,和
  2. 我非常喜欢这种实现方法,希望在更改之前先了解其限制。

有人对我如何解决此问题有任何提示吗?

c++ templates instantiation
1个回答
0
投票

为了直接回答您的问题,虚拟和模板组合使编译器无法懒惰地实现主体copyAndAddInfo。虚拟基本类型指针隐藏了类型信息,因此,当编译器看到other.m_pName->copyAndAddInfo时,它不知道需要延迟实现哪种类型。

编辑:

好的,因此根据您使用模板的原理,您似乎只想接受不同类型的标签,并且可能实际上并不在乎脱节联合信息是否属于该类型。如果是这种情况,您可以将其从名称移至标签,并使其成为运行时信息:

class Label {
public:
    template<class T> Label(const T& name) : m_pName(new Name<T>(name)) {}
    Label(const Label& other) : m_pName(other.m_pName->copy()), m_extraInfo(other.m_extraInfo) { }
    Label(const Label& other, size_t extraInfo) : m_pName(other.m_pName->copy()), m_extraInfo(other.m_extraInfo) {
        m_extraInfo.push_back(extraInfo);
    }
    bool operator==(const Label& other) const { 
        return *m_pName == *other.m_pName && std::equal(
            m_extraInfo.begin(), m_extraInfo.end(), 
            other.m_extraInfo.begin(), other.m_extraInfo.end()); }
private:
    struct NameBase { /* same as before */ };

    std::vector<size_t> m_extraInfo;
    std::unique_ptr<NameBase> m_pName;
};

如果不相交的工会信息很重要,请在下面享受我最初的讽刺回答。原始答案:

就是说,如果您愿意为递归设置上限,那么我为您提供了一个邪恶的解决方案,该解决方案最多可用于N个嵌套级别:使用SFINAE确定嵌套级别并让实现抛出错误或返回nullptr_t以避免进一步生成模板。

首先,计算嵌套级别:

template <typename T, size_t Level> struct CountNestedPairsImpl { static constexpr size_t value = Level; }; template <typename T, size_t Level> struct CountNestedPairsImpl<std::pair<T, size_t>, Level> : CountNestedPairsImpl<T, Level + 1> { using CountNestedPairsImpl<T, Level + 1>::value; }; template <typename T> using CountNestedPairs = CountNestedPairsImpl<T, 0>;

然后,使用std::enable_if<>根据嵌套级别生成不同的实体:

constexpr size_t NESTING_LIMIT = 4; NameBase* copyAndAddInfo(size_t info) const override { return copyAndAddInfoImpl(info); } template <typename U = T, typename std::enable_if<CountNestedPairs<U>::value < NESTING_LIMIT, nullptr_t>::type = nullptr> NameBase* copyAndAddInfoImpl(size_t info) const { return new Name<std::pair<T, size_t>>(std::make_pair(m_name, info)); } template <typename U = T, typename std::enable_if<CountNestedPairs<U>::value >= NESTING_LIMIT, nullptr_t>::type = nullptr> NameBase* copyAndAddInfoImpl(size_t info) const { throw std::runtime_error("too much disjoint union nesting"); }

我为什么称此为邪恶?它会生成允许的所有可能的嵌套级别,因此,如果您使用NESTING_LIMIT=20,它将为每种标签类型生成20个类。但是,至少它可以编译!

https://godbolt.org/z/eaQTzB

© www.soinside.com 2019 - 2024. All rights reserved.