SFINAE使用演绎,但用替换失败
SFINAE works with deduction but fails with substitution
请考虑以下MCVE
struct A {};
template<class T>
void test(T, T) {
}
template<class T>
class Wrapper {
using type = typename T::type;
};
template<class T>
void test(Wrapper<T>, Wrapper<T>) {
}
int main() {
A a, b;
test(a, b); // works
test<A>(a, b); // doesn't work
return 0;
}
在这里test(a, b);
工作,test<A>(a, b);
失败,并显示:
<source>:11:30: error: no type named 'type' in 'A'
using type = typename T::type;
~~~~~~~~~~~~^~~~
<source>:23:13: note: in instantiation of template class 'Wrap<A>' requested here
test<A>(a, b); // doesn't work
^
<source>:23:5: note: while substituting deduced template arguments into function template 'test' [with T = A]
test<A>(a, b); // doesn't work
现场演示
问:这是为什么?SFINAE在替代期间不应该起作用吗?然而,在这里,它似乎只在演绎期间起作用。
自我介绍
大家好,我是一个无辜的编译器。
第一个电话
test(a, b); // works
在此调用中,参数类型为A
。让我首先考虑第一个重载:
template <class T>
void test(T, T);
容易。T = A
. 现在考虑第二个:
template <class T>
void test(Wrapper<T>, Wrapper<T>);
嗯 ......什么?Wrapper<T>
A
?我必须为世界上每个可能的T
类型实例化Wrapper<T>
,以确保类型Wrapper<T>
的参数(可能是专用的)不能用类型A
的参数初始化?嗯 ......我不认为我会那样做 ...
因此,我不会实例化任何Wrapper<T>
.我将选择第一个重载。
第二个电话
test<A>(a, b); // doesn't work
test<A>
?啊哈,我不用演绎。让我检查一下两个重载。
template <class T>
void test(T, T);
T = A
.现在替换 — 签名(A, A)
。完善。
template <class T>
void test(Wrapper<T>, Wrapper<T>);
T = A
.现在实质... 等等,我从来没有实例化过Wrapper<A>
?那我就替代不了了。我如何知道这是否是呼叫的可行过载?好吧,我必须先实例化它。(实例化)等等 ...
using type = typename T::type;
A::type
?错误!
回到L.
大家好,我是L.F。让我们回顾一下编译器做了什么。
编译器是否足够无辜?他(她?)是否符合标准?@YSC指出,[temp.over]/1 说:
写入对函数或函数模板名称的调用时 (显式或隐式使用运算符表示法),模板 参数推导([temp.deduc])和检查任何明确的 为每个函数执行模板参数 ([temp.arg]) 模板以查找可以是 与该函数模板一起使用以实例化函数模板 可以使用调用参数调用的专用化。对于每个 函数模板,如果参数推导和检查成功, 模板参数(推导和/或显式)用于 合成单个函数模板的声明 添加到候选函数集的专业化 用于重载解析。如果,对于给定的函数模板,参数推导失败或合成函数模板失败 专业化格式不正确,没有这样的功能添加到 该模板的候选函数集。全套 候选函数包括所有合成声明和所有 同名的非模板重载函数。这 合成声明被视为与 过载解析的其余部分,除非在 [过度.匹配.最佳]。
缺少type
会导致硬错误。阅读 https://stackoverflow.com/a/15261234。基本上,在确定template<class T> void test(Wrapper<T>, Wrapper<T>)
是否是所需的重载时,我们有两个阶段:
实例。在这种情况下,我们(完全)实例化
Wrapper<A>
.在此阶段,using type = typename T::type;
是有问题的,因为不存在A::type
。此阶段出现的问题是硬错误。替代。由于第一阶段已经失败,因此在这种情况下甚至没有达到此阶段。此阶段发生的问题受SFINAE的影响。
所以是的,无辜的编译器做了正确的事情。
我不是语言律师,但我不认为在类中定义using type = typename T::type;
本身可以用作 SFINAE 来启用/禁用接收该类对象的函数。
如果需要解决方案,可以将SFINAE应用于Wrapper
版本,如下所示
template<class T>
auto test(Wrapper<T>, Wrapper<T>)
-> decltype( T::type, void() )
{ }
这样,此test()
函数仅对内部定义了type
类型的T
启用。
在您的版本中,为每种T
类型启用,但当T
与Wrapper
不兼容时出错。
--编辑--
OP精确并询问
我的包装器对 T 有更多的依赖关系,在 SFINAE 表达式中将它们全部复制是不切实际的。有没有办法检查包装器本身是否可以实例化?
正如 Holt 所建议的,您可以创建自定义类型特征以查看类型是否是Wrapper<something>
类型;通过示例
template <typename>
struct is_wrapper : public std::false_type
{ };
template <typename T>
struct is_wrapper<Wrapper<T>> : public std::true_type
{ using type = T; };
然后,您可以修改Wrapper
版本以接收U
类型并检查U
是否为Wrapper<something>
类型
template <typename U>
std::enable_if_t<is_wrapper<U>{}> test (U, U)
{ using T = typename is_wrapper<U>::type; }
请注意,您可以使用is_wrapper
结构中的type
定义恢复原始T
类型(如果需要)。
如果您需要非Wrapper
版本的test()
,使用此解决方案,您必须在T
是Wrapper<something>
类型时明确禁用它以避免冲突
template <typename T>
std::enable_if_t<!is_wrapper<T>{}> test(T, T)
{ }
<</div> div class="answers">函数调用表达式中调用的函数的推导分两步执行:
- 确定一组可行的功能;
- 确定最佳可行功能。
可行函数集只能包含函数声明和模板函数专用声明。
所以当一个调用表达式(test(a,b)
或test<A>(a,b)
)命名一个模板函数时,需要确定所有的模板参数:这称为模板参数推导。分三步执行[温度扣除]:
- 替换显式提供的模板参数(
names<A>(x,y)
A
显式提供);(替换意味着在函数模板设计中,模板参数被替换为它们的参数) - 扣除未提供的模板参数;
- 替换推导的模板参数。
调用表达式test(a,b)
- 没有显式提供的模板参数。
T
推导第一个模板函数的A
,则第二个模板函数 [temp.deduct.type]/8 的推导失败。所以第二个模板函数不会参与重载解析A
在第一个模板函数的声明中被替换。替换成功。
因此,集合中只有一个重载,它是通过重载分辨率选择的。
调用表达式test<A>(a,b)
(在@T.C.和@geza的有关评论之后编辑)
- 提供了模板参数:
A
,并在两个模板函数的声明中替换它。此替换仅涉及函数模板专用化声明的实例化。所以两个模板很好 - 不扣除模板参数
- 没有替换推导的模板参数。
因此,两个模板专用化(test<A>(A,A)
和test<A>(Wrapper<A>,Wrapper<A>)
)参与重载解析。首先,编译器必须确定哪些函数是可行的。为此,编译器需要找到一个隐式转换序列,将函数参数转换为函数参数类型 [over.match.viable]/4:
第三,为了使 F 成为一个可行的函数,每个参数都应该存在一个隐式转换序列,该序列将该参数转换为 F 的相应参数。
对于第二个重载,为了找到Wrapper<A>
的转换,编译器需要此类的定义。所以它(隐式)实例化它。正是这种实例化导致了编译器生成的观察到的错误。
- 模板参数替换失败,并且未完成隐式转换
- 要求子句中不允许哪些替换失败?
- 模板参数推导/替换失败,lambda作为函数指针
- 类模板参数推导失败会导致替换失败
- C++ 带有 decltype 的 SFINAE:替换失败成为错误?
- 折叠表达式模板参数推导/替换失败
- 使用"std::function"和先前推断的模板参数替换失败 - 为什么?
- 模板参数推导/替换失败 C++
- 当Boost ::绑定模板函数时,模板参数扣除/替换失败
- SFINAE使用演绎,但用替换失败
- 为什么代码中的模板参数推导/替换失败?-.
- GCC 模板参数推断/替换失败
- 为什么模板参数推导/替换失败
- 模板模板参数的替换失败
- C++模板参数推导/替换失败
- C++模板参数推导/替换失败:
- 使用类型名参数时,模板参数推导/替换失败
- 有没有办法将模板的替换失败转换为布尔值(真/假)或标签(标准::true_type/标准::false_type)
- 模板参数扣除/替换失败-STD :: find()
- std::tuple_cat替换失败