类模板的嵌套模板参数推断不起作用

时间:2017-02-15 08:33:58

标签: c++ templates c++17 class-template argument-deduction

this Q&A中,我编写了一个小包装类,它提供了对范围的反向迭代器访问,依赖于类模板的c ++ 1z语言特征模板参数推导(p0091r3,{{3} })

#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 << ',';    
}

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

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

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的输出相同)

问题:是类模板的嵌套模板参数推导,只能用于#34;一个级别&#34;深,或者这是g ++和clang当前实现中的一个错误?

2 个答案:

答案 0 :(得分:7)

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

  

形成一组功能和功能模板,包括:

     
      
  • 对于由指定的类模板的每个构造函数    template-name ,一个具有以下属性的函数模板:
  •   
     
    
        
  • 模板参数是类模板的模板参数,后跟模板参数(包括默认值)     构造函数的模板参数,如果有的话。

  •     
  • 函数参数的类型是构造函数的类型。

  •     
  • 返回类型是 template-name 指定的类模板特化和模板对应的模板参数     从类模板中获取的参数。

  •     
  

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

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

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

然后尝试根据调用选择最佳重载:

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

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

Reverse(Reverse(my_stack))

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

答案 1 :(得分:5)

Piotr's answer正确地解释了发生了什么 - 移动构造函数比构造函数模板更好地匹配。

但是(h / t T.C.像往常一样)除了编写工厂之外还有一个更好的解决方法:你可以添加一个明确的演绎指南来处理包装:

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

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

  

鉴于这些定义,如果从演绎指南生成[...] F1,则可行函数F2被定义为比另一个可行函数F1更好的函数( 13.3.1.8)和F2不是。

因此,我们有四个生成的函数,再次从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 << ',';    
}

并且有效。