我有这个代码:
- struct My
- {
- typedef int foo;
- };
- struct My2
- {
- };
- template <typename T>
- void Bar(const T&,int z = typename T::foo())
- {
- std::cout << "My" << std::endl;
- }
- void Bar(...)
- {
- std::cout << "..." << std::endl;
- }
- int main()
- {
- My my;
- Bar(my); // OK
- My2 my2;
- Bar(my2); // Compile error: no type named ‘foo’ in ‘struct My2’
- return 0;
- }
我想,如果一些类T没有typedef foo里面,编译器应该排除第一个重载,并选择带有省略号的重载.但是我在MSVC,gcc和clang上检查这个代码,我在这些编译器上收到编译错误.为什么SFINAE在这种情况下不起作用?
解决方法
z的类型不受模板替换,它总是int.这意味着SFINAE没有机会,而是在尝试解析T :: foo作为默认值时,得到编译器错误.默认参数不参与重载分辨率,而是仅在函数调用缺失时被实例化.该标准的第14.7.1节(第13/14段)描述了这种行为,但并没有给出缺乏SFINAE的理由.
通过将z的类型设置为模板参数,可以允许SFINAE发生,如下所示:
- #include <iostream>
- struct My
- {
- typedef int foo;
- };
- struct My2
- {
- };
- template<typename T,typename I=typename T::foo> void Bar(const T&,I z = I())
- {
- std::cout << "My\n";
- }
- void Bar(...)
- {
- std::cout << "...\n";
- }
- int main()
- {
- My my;
- Bar(my); // OK
- My2 my2;
- Bar(my2); // Also OK
- return 0;
- }
这将使用第一个呼叫的“我的”版本,第二个呼叫的“…”版本.输出是
- My
- ...
然而,如果void Bar(…)是一个模板,无论什么原因,“My”版本永远都不会有机会:
- #include <iostream>
- struct My
- {
- typedef int foo;
- };
- struct My2
- {
- };
- template<typename T,I z = I())
- {
- std::cout << "My\n";
- }
- template<typename T> void Bar(T&)
- {
- std::cout << "...\n";
- }
- int main()
- {
- My my;
- Bar(my); // OK
- My2 my2;
- Bar(my2); // Also OK
- return 0;
- }
- ...
- ...
一个解决方案是使用类模板(部分)专业化;提供“…”版本作为基础,第二个参数的类型默认为int,“My”版本作为专用化,第二个参数是typename T :: foo.结合一个简单的模板函数来推导T并发送到适当的类’成员函数,这产生了期望的效果:
- #include <iostream>
- struct My
- {
- typedef int foo;
- };
- struct My2
- {
- };
- template<typename T,typename I=int> struct call_traits {
- static void Bar(...)
- {
- std::cout << "...\n";
- }
- };
- template<typename T> struct call_traits<T,typename T::foo> {
- static void Bar(const T&,int z=typename T::foo())
- {
- std::cout << "My\n";
- }
- };
- template<typename T> void Bar(const T& t)
- {
- call_traits<T>::Bar(t);
- }
- int main()
- {
- My my;
- Bar(my); // OK
- My2 my2;
- Bar(my2); // Still OK
- return 0;
- }
这里输出的是:
- My
- ...