Tem*_*Rex 13 c++ templates class-template argument-deduction c++17
在这个Q&A中,我编写了一个小包装类,它提供了对范围的反向迭代器访问,依赖于类模板的c ++ 1z语言特征模板参数推导(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 << ',';
}
}
Run Code Online (Sandbox Code Playgroud)
但是,执行嵌套应用程序Reverse不会产生原始迭代顺序
// still prints 3,2,1 instead of 1,2,3
for (auto const& elem : Reverse(Reverse(my_stack))) {
std::cout << elem << ',';
}
Run Code Online (Sandbox Code Playgroud)
实例(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 << ',';
}
Run Code Online (Sandbox Code Playgroud)
实例(g ++和clang的相同输出)
问题:类模板的嵌套模板参数推导是否只能在"一级"深度工作,或者这是g ++和clang当前实现中的错误?
这可以在[over.match.class.deduct]/p1中解释:
形成一组功能和功能模板,包括:
- 对于template-name指定的类模板的每个构造 函数,具有以下属性的函数模板:
模板参数是类模板的模板参数,后跟构造函数的模板参数(包括默认模板参数)(如果有).
函数参数的类型是构造函数的类型.
返回类型是由模板名称和模板参数指定的类模板特化,其对应于从类模板获得的模板参数.
我的理解是编译器发明了以下两个函数(两个 - 包括为此类隐式生成的复制构造函数):
template <typename Rng>
Reverse<Rng> foo(const Rng& r); // #1
template <typename Rng>
Reverse<Rng> foo(const Reverse<Rng>& r); // #2
Run Code Online (Sandbox Code Playgroud)
然后尝试根据调用选择最佳重载:
foo(Reverse<std::vector<int>>(my_stack));
Run Code Online (Sandbox Code Playgroud)
解析为#2,因为这个更专业.结论是:
Reverse(Reverse(my_stack))
Run Code Online (Sandbox Code Playgroud)
涉及一个复制构造函数来构造外部Reverse实例.
Piotr的答案正确地解释了发生了什么 - 移动构造函数比构造函数模板更好.
但是(通常情况下是TC)除了编写工厂之外还有一个更好的解决方法:你可以添加一个明确的演绎指南来处理包装:
template <class R>
Reverse(Reverse<R> ) -> Reverse<Reverse<R>>;
Run Code Online (Sandbox Code Playgroud)
这一点是要覆盖复制扣除候选者,这要归功于[over.match.best]中新添加的首选项:
鉴于这些定义,如果[...] 从演绎指南(13.3.1.8)生成而
F1不是,则可行函数被定义为比另一个可行函数更好的函数.F2F1F2
因此,我们有两个生成的函数,再次从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
Run Code Online (Sandbox Code Playgroud)
以前,#3更喜欢更专业.现在,#4首选作为演绎指南.所以,我们仍然可以写:
for (auto const& elem : Reverse(Reverse(my_stack))) {
std::cout << elem << ',';
}
Run Code Online (Sandbox Code Playgroud)
这很有效.