【问题标题】:Stop an increasing infinite recursive template instantiation, that is not needed停止不断增加的无限递归模板实例化,这是不需要的
【发布时间】:2019-11-30 16:32:46
【问题描述】:

我正在实现一个图形类,每个顶点都有一个不一定相同类型的标签。我希望用户能够提供任何标签(在编译时),而不需要 Graph 或 Vertex 知道类型是什么。为此,我使用了隐藏在 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;
};

用户(又名我)的一个要求是能够创建图的不相交并集(他已经能够创建双图,图的并集(具有相同标签的顶点映射到相同的顶点) , ETC。)。希望新图的标签是旧标签和某个整数的对,表示标签来自哪个图(这也确保新标签都是不同的)。为此,我认为我可以使用 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&lt;int&gt;,但是函数

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

只有在我调用它的时候才会被定义。 (因此,Name&lt;std::pair&lt;int, size_t&gt;&gt; 只会按需实例化)

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

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

  1. 我不想手动跟踪用户需要的类型,并且
  2. 我非常喜欢这种实现方法,并希望在更改之前先看看它的局限性。

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

【问题讨论】:

  • 您希望能够重复此构造吗?如,您是否希望能够拥有Name&lt;std::pair&lt;std::pair&lt;T, size_t&gt;, size_t&gt;。如果没有,您可以为std::pair&lt;T, size_t&gt; 添加一个Name 的部分规范(或者最好是自己的类型,否则,如果有人想用一对标记,则会出现意外行为)。
  • 是的,理论上我希望能够任意采用许多不相交的联合(不是我真的要这样做)。但是,我认为这可能是明智的(当然,使用自定义结构,而不是一对)。也许编译器会选择使用通用模板类,只要用户调用相应的 copyAndAddInfo() 函数并在不再使用该函数时退回到专业化中。我会尝试一下,让你知道。谢谢:)
  • 我不认为你想做的事情是可能的。如果您在任何地方都有Name&lt;T&gt;,编译器将为Name&lt;std::pair&lt;T, size_t&gt;&gt; 创建代码,因为它必须创建需要此类型的copyAndAddInfo 方法。但是,它必须创建下一个递归等等。具体来说,其深度在编译时是未知的。例如,考虑以下代码:std::unique_ptr&lt;NodeBase&gt; ptr; for(auto i = 0; i &lt; n; ++i) { ptr = ptr-&gt;copyAndAddInfo();},其中n 是一些运行时输入。

标签: c++ templates instantiation


【解决方案1】:

为了直接回答您的问题,虚拟和模板组合使编译器无法懒惰地实现主体 copyAndAddInfo。虚拟基类型指针隐藏了类型信息,所以当编译器看到other.m_pName-&gt;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 在 N 级后抛出错误,而不是永远递归。

首先,计算嵌套层数:

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&lt;&gt;根据嵌套级别生成不同的body:

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

【讨论】:

  • 首先,编辑后的解决方案完全解决了这个特殊问题。我在 Label 类中拥有的一些额外功能,例如散列和打印标签,可以很容易地修改以考虑这个额外的成员。现在这似乎是一个明显的解决方案,我从未考虑过。非常感谢。 :) 话虽如此,我认为您的原始答案实际上可能更有用(无论多么邪恶),因为我想尝试 sfinae 很长一段时间,但我没有自己的例子来做到这一点,所以也非常感谢你。
  • 感谢@D.Bogiokas 的客气话。不过我做了一个小修正:计数技巧是使用partial specialization,而不是 SFINAE。 SFINAE 位使用enable_if 将编译时条件添加到copyAndAddInfoImpl
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-22
  • 1970-01-01
  • 1970-01-01
  • 2021-01-03
  • 1970-01-01
相关资源
最近更新 更多