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 不应该在 替换 期间工作吗?然而在这里它似乎只在推导期间有效。
我不是语言律师,但我不认为在 class 中定义 using type = typename T::type;
本身可用作 SFINAE 以 enable/disable 接收一个函数class.
的对象
如果你想要一个解决方案,你可以将SFINAE应用到Wrapper
版本如下
template<class T>
auto test(Wrapper<T>, Wrapper<T>)
-> decltype( T::type, void() )
{ }
这样,此 test()
函数仅对其中定义了 type
类型的 T
类型启用。
在您的版本中,每个 T
类型都启用,但当 T
与 Wrapper
不兼容时会出错。
-- 编辑 --
OP精问
My Wrapper has many more dependencies on T, it would be impractical to duplicate them all in a SFINAE expression. Isn't there a way to check if Wrapper itself can be instantiated?
正如 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
类型(如果需要)。
如果您需要 test()
的非 Wrapper
版本,使用此解决方案时,当 T
是 Wrapper<something>
类型时,您必须明确禁用它以避免冲突
template <typename T>
std::enable_if_t<!is_wrapper<T>{}> test(T, T)
{ }
自我介绍
大家好,我是无辜的编译器
第一次通话
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.F.
大家好,我是L.F,让我们回顾一下编译器做了什么
编译器够无辜吗?他(她?)达标了吗?
@YSC has pointed out that [temp.over]/1 说:
When a call to the name of a function or function template is written
(explicitly, or implicitly using the operator notation), template
argument deduction ([temp.deduct]) and checking of any explicit
template arguments ([temp.arg]) are performed for each function
template to find the template argument values (if any) that can be
used with that function template to instantiate a function template
specialization that can be invoked with the call arguments. For each
function template, if the argument deduction and checking succeeds,
the template-arguments (deduced and/or explicit) are used to
synthesize the declaration of a single function template
specialization which is added to the candidate functions set to be
used in overload resolution. If, for a given function template,
argument deduction fails or the synthesized function template
specialization would be ill-formed, no such function is added to the
set of candidate functions for that template. The complete set of
candidate functions includes all the synthesized declarations and all
of the non-template overloaded functions of the same name. The
synthesized declarations are treated like any other functions in the
remainder of overload resolution, except as explicitly noted in
[over.match.best].
缺少 type
会导致硬错误。阅读 。基本上,我们有两个阶段来确定 template<class T> void test(Wrapper<T>, Wrapper<T>)
是否是所需的重载:
实例化。在这种情况下,我们(完全)实例化 Wrapper<A>
。在这个阶段,using type = typename T::type;
是有问题的,因为 A::type
是不存在的。 这个阶段出现的问题是硬错误。
换人。由于第一阶段已经失败,在这种情况下甚至没有达到这个阶段。 这个阶段出现的问题以SFINAE为准。
是的,无辜的编译器做了正确的事情。
函数调用表达式中调用函数的推导分两步进行:
- 可行函数集的确定;
- 确定最佳可行函数。
活函数集只能包含函数声明和模板函数特化声明.
所以当调用表达式(test(a,b)
或test<A>(a,b)
)命名模板函数时,需要确定所有模板参数:这称为模板参数推导。这分三个步骤执行 [temp.deduct]:
- Substitution of explicitly provided template arguments (in
names<A>(x,y)
A
is explicitly provided);(substitution是指在函数模板声明中,模板参数被它们的实参替换)
- 扣除未提供的模板参数;
- 替换推导的模板参数。
调用表达式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:
Third, for F to be a viable function, there shall exist for each argument an implicit conversion sequence that converts that argument to the corresponding parameter of F.
对于第二个重载,为了找到到 Wrapper<A>
的转换,编译器需要此 class 的定义。所以它(隐含地)实例化它。这是导致编译器生成的观察到的错误的实例化。
考虑以下 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 不应该在 替换 期间工作吗?然而在这里它似乎只在推导期间有效。
我不是语言律师,但我不认为在 class 中定义 using type = typename T::type;
本身可用作 SFINAE 以 enable/disable 接收一个函数class.
如果你想要一个解决方案,你可以将SFINAE应用到Wrapper
版本如下
template<class T>
auto test(Wrapper<T>, Wrapper<T>)
-> decltype( T::type, void() )
{ }
这样,此 test()
函数仅对其中定义了 type
类型的 T
类型启用。
在您的版本中,每个 T
类型都启用,但当 T
与 Wrapper
不兼容时会出错。
-- 编辑 --
OP精问
My Wrapper has many more dependencies on T, it would be impractical to duplicate them all in a SFINAE expression. Isn't there a way to check if Wrapper itself can be instantiated?
正如 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
类型(如果需要)。
如果您需要 test()
的非 Wrapper
版本,使用此解决方案时,当 T
是 Wrapper<something>
类型时,您必须明确禁用它以避免冲突
template <typename T>
std::enable_if_t<!is_wrapper<T>{}> test(T, T)
{ }
自我介绍
大家好,我是无辜的编译器
第一次通话
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.F.
大家好,我是L.F,让我们回顾一下编译器做了什么
编译器够无辜吗?他(她?)达标了吗? @YSC has pointed out that [temp.over]/1 说:
When a call to the name of a function or function template is written (explicitly, or implicitly using the operator notation), template argument deduction ([temp.deduct]) and checking of any explicit template arguments ([temp.arg]) are performed for each function template to find the template argument values (if any) that can be used with that function template to instantiate a function template specialization that can be invoked with the call arguments. For each function template, if the argument deduction and checking succeeds, the template-arguments (deduced and/or explicit) are used to synthesize the declaration of a single function template specialization which is added to the candidate functions set to be used in overload resolution. If, for a given function template, argument deduction fails or the synthesized function template specialization would be ill-formed, no such function is added to the set of candidate functions for that template. The complete set of candidate functions includes all the synthesized declarations and all of the non-template overloaded functions of the same name. The synthesized declarations are treated like any other functions in the remainder of overload resolution, except as explicitly noted in [over.match.best].
缺少 type
会导致硬错误。阅读 。基本上,我们有两个阶段来确定 template<class T> void test(Wrapper<T>, Wrapper<T>)
是否是所需的重载:
实例化。在这种情况下,我们(完全)实例化
Wrapper<A>
。在这个阶段,using type = typename T::type;
是有问题的,因为A::type
是不存在的。 这个阶段出现的问题是硬错误。换人。由于第一阶段已经失败,在这种情况下甚至没有达到这个阶段。 这个阶段出现的问题以SFINAE为准。
是的,无辜的编译器做了正确的事情。
函数调用表达式中调用函数的推导分两步进行:
- 可行函数集的确定;
- 确定最佳可行函数。
活函数集只能包含函数声明和模板函数特化声明.
所以当调用表达式(test(a,b)
或test<A>(a,b)
)命名模板函数时,需要确定所有模板参数:这称为模板参数推导。这分三个步骤执行 [temp.deduct]:
- Substitution of explicitly provided template arguments (in
names<A>(x,y)
A
is explicitly provided);(substitution是指在函数模板声明中,模板参数被它们的实参替换) - 扣除未提供的模板参数;
- 替换推导的模板参数。
调用表达式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:
Third, for F to be a viable function, there shall exist for each argument an implicit conversion sequence that converts that argument to the corresponding parameter of F.
对于第二个重载,为了找到到 Wrapper<A>
的转换,编译器需要此 class 的定义。所以它(隐含地)实例化它。这是导致编译器生成的观察到的错误的实例化。