包装递归可变参数模板类会更改行为.为什么?

Wrapping a recursive variadic template class changes behavior. Why?

本文关键字:为什么 递归 变参 参数 包装      更新时间:2023-10-16

希望这能引起社区中的一些人的兴趣。希望它不是太明显,因为我不确定发生了什么。我创建了具有递归定义的可变参数模板类,主要作为一个有趣的自我挑战。有点像元组,此类创建unordered_maps unordered_maps,具有任意深度,并在每层使用任意键类型。因此,例如,您可以创建nested_map<int, std::string, float, int>然后使用map["fred"][3.4][42] = 35;这是代码 - 不太疯狂。

template<typename T, typename K, typename ... KS> struct nested_map_base : std::unordered_map<K, T>
{
T &operator[](const K &key)
{
// just to verify we get to the bottom of things recursively
std::cout << "base: key = " << key << std::endl;
return this->std::unordered_map<K, T>::operator[](key);
}
};
template<typename T, typename New_K, typename K, typename ... KS>
struct nested_map_base<T, New_K, K, KS ...>
: std::unordered_map<New_K, nested_map_base<T, K, KS...>>
{
nested_map_base<T, K, KS...> &operator[](const New_K &new_key)
{
// just for debugging and to demonstrate that it's working
// for purposes of this question
std::cout << "midway: key = " << new_key << std::endl;
return this->std::unordered_map<New_K, nested_map_base<T, K, KS...>>::operator[](new_key);
}
};

运行以下代码,获取预期的输出 -

std::cout << "Method1:" << std::endl << std::endl;
nested_map_base<int, std::string, double, int> test_nest;
std::cout << "insert" << std::endl;
test_nest["leonard"][4.8][45] = 111;
std::cout << "retrieve" << std::endl;
int &answer = test_nest["leonard"][4.8][45];
std::cout << "Aanswer should be 111. Answer is " << answer << std::endl << std::endl;

生产-

Method1:
insert
midway: key = leonard
midway: key = 4.8
base: key = 45
retrieve
midway: key = leonard
midway: key = 4.8
base: key = 45
Aanswer should be 111. Answer is 111

整洁。然后我想我想把它包装在一个外部类中以保持实现的私有性,所以我就这样开始

——
template<typename datum_type, typename ... keys> class nested_map
{
private:
nested_map_base<datum_type, keys ...> backing_store;
public:
template<typename Base_key, typename ... KS> auto operator[](const Base_key &key)
{
return backing_store[key];
}
};

那里没什么,起初它似乎有效,但以下代码产生不同的结果 -

std::cout << "Method2:" << std::endl << std::endl;
nested_map<int, std::string, double, int> test_nest;
std::cout << "insert" << std::endl;
test_nest["leonard"][4.8][45] = 111;
std::cout << "retrieve" << std::endl;
int &answer = test_nest["leonard"][4.8][45];
std::cout << "Answer should be 111. Answer is " << answer << std::endl << std::endl;

它产生这个 -

Method2:
insert
midway: key = leonard
midway: key = 4.8
base: key = 45
retrieve
midway: key = leonard
midway: key = 4.8
base: key = 45
Answer should be 111. Answer is 0

递归可变参数模板元编程充满了陷阱,并且有原因导致事情不经常被包装,所以我对包装的模板不起作用并不感到震惊,但让我感到惊讶的是它是如何不起作用的。它按预期递归,一直递归到包含终端基准类型的std::unordered_map。在调试器中,从终端映射中恢复了对 int 的引用,并在简单测试代码中将其设置为 111。您看到键再次递归的事实表明检索过程似乎也在工作,但引用的是零值 int

。 好奇。例如,我正在调试器中深入研究,以查看设置引用的实际地址值是否与用于检索的引用相同。我认为它们可能不同的唯一方法是,例如,倒数第二个递归层返回最后一层的温度,而不是对数据结构中的引用。或者也许在包装的情况下,它们都是临时的而不是参考......类似的东西,但它的包装太轻了,似乎是不可能的。因此,如果我发现更多,我会添加评论,但我想我会把它扔给社区,看看是否有一些不同的眼睛可以通过检查来梳理出来的东西。

在模板参数推导的 Cppreference 页面中有一个关于自动返回函数的部分,描述了将auto用作函数返回时的规则。

模板参数推导用于函数声明中,当从return语句推断函数return类型中auto说明符的含义时。

对于自动返回函数,参数P的获取如下:在T中,声明的函数的返回类型包括 auto,每次出现 auto 都替换为虚构类型模板参数U。参数A是 return 语句的表达式,如果 return 语句没有操作数,则Avoid()。从P中扣除U并按照上述规则A后,将推导出的U替换为T以获得实际的返回类型。

这可以解释为什么auto&有效而auto无效。