c – 为什么SFINAE在默认函数参数中不在右侧工作?

前端之家收集整理的这篇文章主要介绍了c – 为什么SFINAE在默认函数参数中不在右侧工作?前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我有这个代码
  1. struct My
  2. {
  3. typedef int foo;
  4. };
  5.  
  6. struct My2
  7. {
  8. };
  9.  
  10.  
  11. template <typename T>
  12. void Bar(const T&,int z = typename T::foo())
  13. {
  14. std::cout << "My" << std::endl;
  15. }
  16.  
  17.  
  18. void Bar(...)
  19. {
  20. std::cout << "..." << std::endl;
  21. }
  22.  
  23. int main()
  24. {
  25. My my;
  26. Bar(my); // OK
  27. My2 my2;
  28. Bar(my2); // Compile error: no type named ‘foo’ in ‘struct My2’
  29. return 0;
  30. }

我想,如果一些类T没有typedef foo里面,编译器应该排除第一个重载,并选择带有省略号的重载.但是我在MSVC,gcc和clang上检查这个代码,我在这些编译器上收到编译错误.为什么SFINAE在这种情况下不起作用?

解决方法

z的类型不受模板替换,它总是int.这意味着SFINAE没有机会,而是在尝试解析T :: foo作为默认值时,得到编译器错误.默认参数不参与重载分辨率,而是仅在函数调用缺失时被实例化.该标准的第14.7.1节(第13/14段)描述了这种行为,但并没有给出缺乏SFINAE的理由.

通过将z的类型设置为模板参数,可以允许SFINAE发生,如下所示:

(实例:http://ideone.com/JynMye)

  1. #include <iostream>
  2.  
  3. struct My
  4. {
  5. typedef int foo;
  6. };
  7.  
  8. struct My2
  9. {
  10. };
  11.  
  12. template<typename T,typename I=typename T::foo> void Bar(const T&,I z = I())
  13. {
  14. std::cout << "My\n";
  15. }
  16.  
  17. void Bar(...)
  18. {
  19. std::cout << "...\n";
  20. }
  21.  
  22. int main()
  23. {
  24. My my;
  25. Bar(my); // OK
  26. My2 my2;
  27. Bar(my2); // Also OK
  28. return 0;
  29. }

这将使用第一个呼叫的“我的”版本,第二个呼叫的“…”版本.输出

  1. My
  2. ...

然而,如果void Bar(…)是一个模板,无论什么原因,“My”版本永远都不会有机会:

(实例:http://ideone.com/xBQiIh)

  1. #include <iostream>
  2.  
  3. struct My
  4. {
  5. typedef int foo;
  6. };
  7.  
  8. struct My2
  9. {
  10. };
  11.  
  12. template<typename T,I z = I())
  13. {
  14. std::cout << "My\n";
  15. }
  16.  
  17. template<typename T> void Bar(T&)
  18. {
  19. std::cout << "...\n";
  20. }
  21.  
  22. int main()
  23. {
  24. My my;
  25. Bar(my); // OK
  26. My2 my2;
  27. Bar(my2); // Also OK
  28. return 0;
  29. }

在这两种情况下,都会调用“…”版本.输出为:

  1. ...
  2. ...

一个解决方案是使用类模板(部分)专业化;提供“…”版本作为基础,第二个参数的类型默认为int,“My”版本作为专用化,第二个参数是typename T :: foo.结合一个简单的模板函数来推导T并发送到适当的类’成员函数,这产生了期望的效果

(实例:http://ideone.com/FanLPc)

  1. #include <iostream>
  2.  
  3. struct My
  4. {
  5. typedef int foo;
  6. };
  7.  
  8. struct My2
  9. {
  10. };
  11.  
  12. template<typename T,typename I=int> struct call_traits {
  13. static void Bar(...)
  14. {
  15. std::cout << "...\n";
  16. }
  17. };
  18.  
  19. template<typename T> struct call_traits<T,typename T::foo> {
  20. static void Bar(const T&,int z=typename T::foo())
  21. {
  22. std::cout << "My\n";
  23. }
  24. };
  25.  
  26. template<typename T> void Bar(const T& t)
  27. {
  28. call_traits<T>::Bar(t);
  29. }
  30.  
  31. int main()
  32. {
  33. My my;
  34. Bar(my); // OK
  35. My2 my2;
  36. Bar(my2); // Still OK
  37. return 0;
  38. }

这里输出的是:

  1. My
  2. ...

猜你在找的C&C++相关文章