在下面的代码中,我正在尝试构建一个类型的网格。例如,在float
和int
之间,将结果提升为float
:
float join(float f, int) { return f; }
float join(float f, float) { return f; }
然后我介绍一个wrapper
类型:
template <typename Inner>
struct wrapper
{
using inner_t = Inner;
inner_t value;
};
join
操作的行为非常自然:
template <typename Inner1, typename Inner2>
auto
join(const wrapper<Inner1>& w1, const wrapper<Inner2>& w2)
-> wrapper<decltype(join(w1.value, w2.value))>
{
return {join(w1.value, w2.value)};
}
也可以join
使用“标量”类型:
template <typename Inner1, typename T2>
auto
join(const wrapper<Inner1>& w1, const T2& value2)
-> wrapper<decltype(join(w1.value, value2))>
{
return {join(w1.value, value2)};
}
到目前为止,这么好,它有效。但是,因为在实际情况下我实际上有更多这样的规则,我想避免重复表达join
操作的交换性的规则数量,因此,我表达{{1} (事实上,我更喜欢像join(scalar, wrapper) := join(wrapper, scalar)
这样的东西,但让我们从更具体的东西开始。):
join(v1, v2) := join(v2, v1)
这适用于template <typename T1, typename Inner2>
auto
join(const T1& value1, const wrapper<Inner2>& w2)
-> decltype(join(w2, value1))
{
return join(w2, value1);
}
,join(scalar, scalar)
和join(wrapper, scalar)
。但是join(scalar, wrapper)
导致模板函数无限扩展,包括G ++ 4.9和Clang ++ 3.5,我不明白。
join(wrapper, wrapper)
锵:
int main()
{
int i;
float f;
wrapper<float> fr;
join(f, i);
join(fr, i);
join(i, fr);
join(fr, fr); // Loops.
}
GCC
clang++-mp-3.5 -std=c++11 bar.cc
bar.cc:21:5: fatal error: recursive template instantiation exceeded maximum depth of
256
join(const wrapper<Inner1>& w1, const T2& value2)
^
bar.cc:29:5: note: while substituting deduced template arguments into function
template 'join' [with T1 = wrapper<float>, Inner2 = float]
join(const T1& value1, const wrapper<Inner2>& w2)
^
我不明白为什么重载不会减少递归。到底是怎么回事?可能有一个可能的替代实现与(类)模板专业化,但我不是在寻找替代实现:我想了解这个有什么问题。提前谢谢。
答案 0 :(得分:4)
这有几个问题,其中一个导致错误。
template <typename Inner1, typename T2>
auto
join(const wrapper<Inner1>& w1, const T2& value2) // (A)
-> wrapper<decltype(join(w1.value, value2))>;
此处join
的名称查找将无法通过非限定查找找到相同的函数模板,因为 trailing-return-type 是声明的一部分,并且只能找到名称一旦 宣布。但语法允许ADL找到相同的函数模板。依赖名称的ADL稍后执行(从实例化开始)。
据我了解问题,问题来自重载解析:在decltype(join(w1.value, value2))
尝试解析为重载之前,需要实例化具有该名称的所有函数模板。对于每个函数模板,将一个实例添加到重载集(如果实例化成功)。
因此,所有join
都需要实例化。实例化包括确定返回类型。对于此特定join
函数模板(A)的每个实例化,具有相同模板参数的相同函数模板(A)是重载决策集的候选者。 也就是说,要确定哪种返回类型(A),需要有一个重载决策,这需要确定(A)的返回类型等等。
扣除&amp;在递归的任何一个步骤中,替换永远不会失败,不选择此重载的唯一原因是名为join
的不同函数模板之间的部分排序。只有作为重载决策过程的一部分才会检查部分排序 - 这对于阻止进一步实例化来说为时已晚。
错误消息中提到的此错误是作为实现限制发生的。因此,它不属于SFINAE类别,请参阅Solving the SFINAE problem for expressions。因此,即使没有选择这种过载,它的存在也会使程序形成错误,就像
一样struct tag_for_ADL {};
template<class T>
auto foo(T p) -> decltype(foo(p));
foo(tag_for_ADL{}); // ill-formed, leads to infinite recursive instantiation
答案 1 :(得分:0)
定义join_traits
模板。专门用于标量和包装。使用
join_traits<T1>::type
join_traits<T2>::type
和/或
join_traits<T1>::get_value(v1)
join_traits<T2>::get_value(v2)
单个功能模板中的
template<class T1, class T2>
auto join (T1 v1, T2 v2) -> ...