简体   繁体   English

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

[英]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's return type, from the return statement. 模板参数推导是在功能,演绎的含义是,当使用申报auto在函数的说明符return类型,从return的语句。

For auto-returning functions, the parameter P is obtained as follows: in T , the declared return type of the function that includes auto, every occurrence of auto is replaced with an imaginary type template parameter U . 对于自动返回函数,参数P如下获得:在T ,包含auto的函数的声明的返回类型,每次出现的auto都被虚构的模板参数U替换。 The argument A is the expression of the return statement, and if the return statement has no operand, A is void() . 参数A是return语句的表达式,如果return语句没有操作数,则Avoid() After deduction of U from P and A following the rules described above, the deduced U is substituted into T to get the actual return type. 按照上述规则从PA扣除U后,将推导出的U代入T以得到实际的返回类型。

That would explain why auto& works and auto does not. 这可以解释为什么auto&工程和auto没有。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM