[英]Wrapping a recursive variadic template class changes behavior. Why?
Hopefully this will intrigue some in the community. 希望这会激起社区中的一些人的兴趣。 Hope it's not too obvious, because I'm not sure what's going on.
希望它不是太明显,因为我不确定发生了什么。 I created variadic template class with a recursive definition, mostly as an interesting self-challenge.
我创建了带有递归定义的可变参数模板类,主要是作为一个有趣的自我挑战。 Sort of like a tuple, this class creates unordered_maps of unordered_maps, to arbitrary depth and with with arbitrary key types at each layer.
有点像元组一样,这个类创建unordered_maps的unordered_maps,任意深度,每层都有任意键类型。 So you could, for example, create
nested_map<int, std::string, float, int>
and then set it with map["fred"][3.4][42] = 35;
例如,您可以创建
nested_map<int, std::string, float, int>
,然后使用map["fred"][3.4][42] = 35;
设置它map["fred"][3.4][42] = 35;
Here's the code - not too crazy. 这是代码 - 不是太疯狂。
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);
}
};
Works OK. 工作正常。 Running the following code, get's the expected output -
运行以下代码,得到预期的输出 -
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;
produces - 生产 -
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
Neat. 整齐。 Then I thought I'd like to wrap it in an outer class to keep the implementation private, so I just started like this -
然后我想我想将它包装在外部类中以保持实现私有,所以我就这样开始 -
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];
}
};
Nothing much there, and at first it seemed to work, but the following code produces different results - 没有什么,并且起初它似乎工作,但以下代码产生不同的结果 -
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;
It produces this - 它产生了这个 -
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
Recursive variadic template meta-programming is filled with pitfalls, and there are reasons things don't get wrapped very often, so I wasn't shocked that the wrapped one didn't work, but what did surprise me is HOW it didn't work. 递归可变参数模板元编程充满了陷阱,并且有理由不会经常包装,所以我并不感到震惊的是包裹的那个不起作用,但让我感到惊讶的是它怎么没有工作。 It recursed as expected, right down to the
std::unordered_map
that contained the terminal datum type. 它按预期递归,直到包含终端基准类型的
std::unordered_map
。 In the debugger a reference to an int was recovered from the terminal map, and it was set to 111 in the simple test code. 在调试器中,从终端映射中恢复了对int的引用,并且在简单测试代码中将其设置为111。 The fact that you see the keys being recursed a second time indicates that the retrieval process seemed to be working as well, but the reference was to a zero-valued int.
您第二次看到键被递归的事实表明检索过程似乎也在起作用,但引用的是零值int。 Curious.
好奇。
I'm digging deeper in the debugger to see if, for example, the actual address value of the set reference is the same as the reference used to retrieve. 我正在深入挖掘调试器,以查看,例如,集合引用的实际地址值是否与用于检索的引用相同。 The only way that they could be different I would think is if for example, the penultimate recursive layer was returning a temp of the final layer, in stead of a reference to the one in the data structure.
我认为它们可能不同的唯一方法是,例如,倒数第二个递归层返回最后一层的临时值,而不是引用数据结构中的那个。 Or maybe in the wrapped case they're all temps in stead of references... something like that, but it the wrapping is so light, it doesn't seem possible.
或者也许在包装的情况下,它们都是临时的而不是参考......类似的东西,但包装是如此轻,似乎不可能。 So I'll add comments if I find out more, but I thought I'd throw it out to the community to see if there is something that different sets of eyes can tease out by inspection.
所以如果我发现更多内容,我会添加评论,但我想我会把它扔给社区,看看是否有一些不同的眼睛可以通过检查来挑逗。
There is a section on auto-returning functions in the Cppreference page on Template Argument Deduction that describes the rules when auto
is used as return for functions. 在“ 模板参数演绎”的“Cppreference”页面中有一个关于自动返回功能的部分,该部分描述了当
auto
用作函数返回时的规则。
Template argument deduction is used in declarations of functions, when deducing the meaning of the
auto
specifier in the function'sreturn
type, from thereturn
statement.模板参数推导是在功能,演绎的含义是,当使用申报
auto
在函数的说明符return
类型,从return
的语句。For auto-returning functions, the parameter
P
is obtained as follows: inT
, the declared return type of the function that includes auto, every occurrence of auto is replaced with an imaginary type template parameterU
.对于自动返回函数,参数
P
如下获得:在T
,包含auto的函数的声明的返回类型,每次出现的auto都被虚构的模板参数U
替换。 The argumentA
is the expression of the return statement, and if the return statement has no operand,A
isvoid()
.参数
A
是return语句的表达式,如果return语句没有操作数,则A
是void()
。 After deduction ofU
fromP
andA
following the rules described above, the deducedU
is substituted intoT
to get the actual return type.按照上述规则从
P
和A
扣除U
后,将推导出的U
代入T
以得到实际的返回类型。
That would explain why auto&
works and auto
does not. 这可以解释为什么
auto&
工程和auto
没有。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.