class 模板的嵌套模板参数推导不起作用

Nested template argument deduction for class templates not working

this Q&A I wrote a little wrapper class that provides reverse iterator access to a range, relying on the c++1z language feature template argument deduction for class templates (p0091r3, p0512r0)

#include <iostream>
#include <iterator>
#include <vector>

template<class Rng>
class Reverse
{
    Rng const& rng;    
public:    
    Reverse(Rng const& r) noexcept
    : 
        rng(r)
    {}

    auto begin() const noexcept { using std::end; return std::make_reverse_iterator(end(rng)); }
    auto end()   const noexcept { using std::begin; return std::make_reverse_iterator(begin(rng)); }
};

int main()
{
    std::vector<int> my_stack;
    my_stack.push_back(1);
    my_stack.push_back(2);
    my_stack.puhs_back(3);

    // prints 3,2,1
    for (auto const& elem : Reverse(my_stack)) {
        std::cout << elem << ',';    
    }
}

但是,Reverse 的嵌套应用不会产生原始迭代顺序

// still prints 3,2,1 instead of 1,2,3
for (auto const& elem : Reverse(Reverse(my_stack))) {
    std::cout << elem << ',';    
}

Live Example(g++ 7.0 SVN 和 clang 5.0 SVN 的输出相同)

罪魁祸首似乎是 class 模板的模板参数推导,因为通常的包装函数确实允许正确嵌套

template<class Rng>
auto MakeReverse(Rng const& rng) { return Reverse<Rng>(rng); }

// prints 1,2,3
for (auto const& elem : MakeReverse(MakeReverse(my_stack))) {
    std::cout << elem << ',';    
}

Live Example(g++ 和 clang 的输出相同)

问题:class 模板的嵌套模板参数推导应该只能在 "one level" 深度工作,或者这是当前实现中的错误g++ 和 clang?

这可以在 [over.match.class.deduct]/p1 中解释:

A set of functions and function templates is formed comprising:

  • For each constructor of the class template designated by the template-name, a function template with the following properties:
  • The template parameters are the template parameters of the class template followed by the template parameters (including default template arguments) of the constructor, if any.

  • The types of the function parameters are those of the constructor.

  • The return type is the class template specialization designated by the template-name and template arguments corresponding to the template parameters obtained from the class template.

我的理解是编译器发明了以下两个函数(两个 - 包括为此class隐式生成的复制构造函数):

template <typename Rng>
Reverse<Rng> foo(const Rng& r);           // #1

template <typename Rng>
Reverse<Rng> foo(const Reverse<Rng>& r);  // #2

然后尝试 select 基于调用的最佳重载:

foo(Reverse<std::vector<int>>(my_stack));

解析为 #2,因为这个更专业。结论是:

Reverse(Reverse(my_stack))

涉及复制构造函数来构造外部 Reverse 实例。

正确解释了正在发生的事情 - 移动构造函数比您的构造函数模板更匹配。

但是(h/t 和往常一样)有一个比只写一个工厂更好的解决方法:你可以添加一个显式推导指南来处理包装:

template <class R>
Reverse(Reverse<R> ) -> Reverse<Reverse<R>>;

重点是要覆盖复制扣除候选者,这要归功于 [over.match.best] 中为此新添加的首选项:

Given these definitions, a viable function F1 is defined to be a better function than another viable function F2 if [...] F1 is generated from a deduction-guide (13.3.1.8) and F2 is not.

因此,我们将有 四个 生成的函数,再次借用 Piotr 的命名:

template <typename Rng>
Reverse<Rng> foo(const Rng& r);             // #1

template <typename Rng>
Reverse<Rng> foo(const Reverse<Rng>& r);    // #2

template <typename Rng>
Reverse<Rng> foo(Reverse<Rng>&& r);         // #3

template <typename Rng>
Reverse<Reverse<Rng>> foo(Reverse<Rng> r);  // #4 - same-ish as #2/3, but deduction guide

以前,#3 更专业,因此更受欢迎。现在,首选 #4 作为推导指南。所以,我们仍然可以这样写:

for (auto const& elem : Reverse(Reverse(my_stack))) {
    std::cout << elem << ',';    
}

那行得通。