停止增加的无限递归模板实例化,这是不需要的
Stop an increasing infinite recursive template instantiation, that is not needed
我正在实现一个图 class,每个顶点都有一个不一定相同类型的标签。我希望用户能够提供任何标签(在编译时),而无需图形或顶点知道类型是什么。为此,我使用了模板化多态性,我将其隐藏在标签 class 中,以便标签具有值语义。它就像一个魅力,相关代码是这样的(暂时忽略注释部分):
//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 class 的注释部分,但我的 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;
}
编译刚刚冻结。 (我没有收到我看到其他人收到的消息 "maximum template recursion capacity exceeded",但它显然试图实例化所有内容)。我试图将函数分离到另一个 class 中,并仅显式初始化所需的模板,以欺骗编译器,但没有效果。
所需的行为(我不知道是否可能)是实例化使用的模板 classes(连同成员函数声明),但延迟定义成员函数,即仅当它们确实存在时被叫到。比如我调用Label(3)
,应该有className<int>
,但是函数
NameBase* Name<int>::copyAndAddInfo(size_t info) const;
只有在我调用它的时候才会被定义。 (因此,Name<std::pair<int, size_t>>
只会按需实例化)
感觉应该是可行的,因为编译器已经按需定义了模板函数。
一个想法是完全改变实现并使用变体,但是
- 我不想手动跟踪用户需要的类型,并且
- 我非常喜欢这种实现方法,并希望在更改之前了解它的局限性。
有人对我如何解决这个问题有任何提示吗?
为了直接回答你的问题,虚拟和模板组合使得编译器无法延迟实现主体 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在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<>
根据嵌套层级生成不同的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 类。但是,嘿,至少它可以编译!
我正在实现一个图 class,每个顶点都有一个不一定相同类型的标签。我希望用户能够提供任何标签(在编译时),而无需图形或顶点知道类型是什么。为此,我使用了模板化多态性,我将其隐藏在标签 class 中,以便标签具有值语义。它就像一个魅力,相关代码是这样的(暂时忽略注释部分):
//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 class 的注释部分,但我的 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;
}
编译刚刚冻结。 (我没有收到我看到其他人收到的消息 "maximum template recursion capacity exceeded",但它显然试图实例化所有内容)。我试图将函数分离到另一个 class 中,并仅显式初始化所需的模板,以欺骗编译器,但没有效果。
所需的行为(我不知道是否可能)是实例化使用的模板 classes(连同成员函数声明),但延迟定义成员函数,即仅当它们确实存在时被叫到。比如我调用Label(3)
,应该有className<int>
,但是函数
NameBase* Name<int>::copyAndAddInfo(size_t info) const;
只有在我调用它的时候才会被定义。 (因此,Name<std::pair<int, size_t>>
只会按需实例化)
感觉应该是可行的,因为编译器已经按需定义了模板函数。
一个想法是完全改变实现并使用变体,但是
- 我不想手动跟踪用户需要的类型,并且
- 我非常喜欢这种实现方法,并希望在更改之前了解它的局限性。
有人对我如何解决这个问题有任何提示吗?
为了直接回答你的问题,虚拟和模板组合使得编译器无法延迟实现主体 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在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<>
根据嵌套层级生成不同的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 类。但是,嘿,至少它可以编译!