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 << ',';
}
那行得通。
在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 functionF2
if [...]F1
is generated from a deduction-guide (13.3.1.8) andF2
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 << ',';
}
那行得通。