MSVC9 SFINAE quirks?

::type>::type,
::type>::type,
Is there some gotcha about metaprogramming with MSVC9? I recently finished my geometry library rewrite and found that I couldn't port it to windows because the SFINAE overloading of generic functions is viewed as ambiguous in the more complex cases, but was readily accepted by gcc and icc in linux. The following code compiles in recent versions of gcc, but fails in MSVC9 //simple SFINAE overload of foo works in both template <typename T> typename gtl_if<typename is_mutable_polygon_90_type<T>::type>::type foo(const T& t) { return gtl_yes(); } template <typename T> typename gtl_if<typename is_mutable_polygon_45_type<T>::type>::type foo(const T& t) { return gtl_yes(); } //metafunction wrapper around SFINAE to supply return type works in both template <typename T> typename requires_1<typename gtl_if<typename is_mutable_polygon_90_type<T>::type>::type, void>::type foo2(const T& t) { } template <typename T> typename requires_1<typename gtl_if<typename is_mutable_polygon_45_type<T>::type>::type, void>::type foo2(const T& t) { } //meta function wrapper around two checks works in gcc but not msvc9 // error C2668: 'gtl::foo4' : ambiguous call to overloaded function template <typename polygon_type, typename polygon_type_2> typename requires_1< typename gtl_if< typename gtl_and< typename is_mutable_polygon_90_type<polygon_type>::type, typename is_mutable_polygon_90_type<polygon_type_2>::type polygon_type>::type foo4(polygon_type& polygon, const polygon_type_2& point) {return point;} template <typename polygon_type, typename polygon_type_2> typename requires_1< typename gtl_if< typename gtl_and< typename is_mutable_polygon_45_type<polygon_type>::type, typename is_mutable_polygon_45_type<polygon_type_2>::type polygon_type>::type foo4(polygon_type& polygon, const polygon_type_2& point) {return point;} int main() { using namespace gtl; polygon_45_data<int> p45; foo(p45); foo2(p45); foo4(p45, p45); //error here //this works as expected and has no syntax error in msvc9 gtl_if<gtl_and< is_mutable_polygon_45_type<polygon_45_data<int> >::type, is_mutable_polygon_45_type<polygon_45_data<int> >::type >::type>::type a; //this works as expected, generating syntax error in msvc9 // error C2039: 'type' : is not a member of 'gtl::gtl_if<gtl::gtl_no> gtl_if<gtl_and< is_mutable_polygon_90_type<polygon_45_data<int> >::type, is_mutable_polygon_90_type<polygon_45_data<int> >::type >::type>::type b; Does someone like Steve Wanatabe with more experience with MS compilers know what I have inadvertently run afoul of and how to get myself out of this jam? Thanks, Luke

AMDG Simonson, Lucanus J wrote:
Is there some gotcha about metaprogramming with MSVC9?
I've run into some bizarre behavior when using SFINAE for class templates, but I don't remember any specific problems with function templates.
I recently finished my geometry library rewrite and found that I couldn't port it to windows because the SFINAE overloading of generic functions is viewed as ambiguous in the more complex cases, but was readily accepted by gcc and icc in linux.
The following code compiles in recent versions of gcc, but fails in MSVC9
<snip>
Does someone like Steve Wanatabe with more experience with MS compilers know what I have inadvertently run afoul of and how to get myself out of this jam?
I can take a look at it if I have a full test case to work with. Where can I get the current version of gtl? https://svn.boost.org/svn/boost/sandbox/gtl is old. In Christ, Steven Watanabe

I can take a look at it if I have a full test case to work with. Where can I get the current version of gtl? https://svn.boost.org/svn/boost/sandbox/gtl is old.
Try it now. You can just add #include "gtl.hpp" at the top of the code snippet from my previous email and you should be able to reproduce the same compiler errors I'm seeing using what is now in the sandbox. Thanks, Luke

AMDG Simonson, Lucanus J wrote:
I can take a look at it if I have a full test case to work with. Where can I get the current version of gtl? https://svn.boost.org/svn/boost/sandbox/gtl is old.
Try it now. You can just add #include "gtl.hpp" at the top of the code snippet from my previous email and you should be able to reproduce the same compiler errors I'm seeing using what is now in the sandbox.
Okay. So msvc just doesn't like SFINAE except at the top level. I think you're going to have to make all the tests boolean metafunctions and add a top level enable_if. For example: template <typename geometry_type_1, typename geometry_type_2> typename boost::enable_if< boost::mpl::and_<is_any_polygon_set_type<geometry_type_1>, is_any_polygon_set_type<geometry_type_2>, is_either_polygon_set_type<geometry_type_1, geometry_type_2> >, polygon_set_view<geometry_type_1, geometry_type_2, 3>
::type operator-(const geometry_type_1& lvalue, const geometry_type_2& rvalue) { return polygon_set_view<geometry_type_1, geometry_type_2, 3> (lvalue, rvalue); }
In Christ, Steven Watanabe

Steven Watanabe wrote:
I can take a look at it if I have a full test case to work with. Where can I get the current version of gtl? https://svn.boost.org/svn/boost/sandbox/gtl is old.
Simonson, Lucanus J wrote:
Try it now. You can just add #include "gtl.hpp" at the top of the code >>snippet from my previous email and you should be able to reproduce the >>same compiler errors I'm seeing using what is now in the sandbox.
Steven Watanabe wrote:
Okay. So msvc just doesn't like SFINAE except at the top level. I think you're going to have to make all the tests boolean metafunctions and add a top level enable_if.
It turns out the problem was that my meta logic was written so that any type was true and only gtl_no was false. When I wanted to use meta logic for SFINAE the default template was defining "type" and it was specialized (or partially specialized) for gtl_no to not define "type" in that case. MSVC9 seems to have not instantiated the template fully and just assumed that since the default at the top level defined "type" then substitution would eventually succeed and flagged the two functions as ambiguous. By changing requires_1 so that default does not define "type" and only the specialization for gtl_yes defines "type" I got it to work. This requires me to rewrite some of my code, however, to move all SFINAE to the top level. In my original example I sent to the list requires_1 worked in a one template parameter case, but failed when there were two. There seems to be an inconsistency in the behavior of the compiler. Do you think this is a problem in the MSVC compiler, or are they doing the right thing, or is it a grey area in the standard? Thanks, Luke

Steven Watanabe wrote:
template <typename geometry_type_1, typename geometry_type_2> typename boost::enable_if< boost::mpl::and_<is_any_polygon_set_type<geometry_type_1>, is_any_polygon_set_type<geometry_type_2>, is_either_polygon_set_type<geometry_type_1, geometry_type_2> >, polygon_set_view<geometry_type_1, geometry_type_2, 3>
::type operator-(const geometry_type_1& lvalue, const geometry_type_2& rvalue) { return polygon_set_view<geometry_type_1, geometry_type_2, 3> (lvalue, rvalue); }
This won't work. In order to instantiate enable_if the compiler must instantiate both of its arguments. That means the compiler will try to instantiate polygon_set_view<> for every time the generic operator - in the stl is used for iterator arithmetic. Any error encountered trying to instantiate polygon_set_view will cause a syntax error and failed compilation even though the and_ evaluates to false. To get around this I have added substitution failure when the first parameter is parsed: template <typename geometry_type_1, typename geometry_type_2> typename requires_1< typename gtl_if<typename gtl_and< typename is_polygon_90_set_type<geometry_type_1>::type, typename is_polygon_90_set_type<geometry_type_2>::type>::type>::type, polygon_90_set_view<geometry_type_1, geometry_type_2, boolean_op::BinaryNot> >::type operator-(const geometry_type_1& lvalue, const geometry_type_2& rvalue) { return polygon_90_set_view<geometry_type_1, geometry_type_2, boolean_op::BinaryNot> (lvalue, rvalue, polygon_90_set_traits<geometry_type_1>::orient(lvalue), boolean_op::BinaryNot()); } requires_1 is equivalent to enable_if, gtl_if returns causes substitution failure unless it receives gtl_yes. I'm hoping MSVC will accept this syntax; gcc does just fine. Regards, Luke

AMDG Simonson, Lucanus J wrote:
Steven Watanabe wrote:
template <typename geometry_type_1, typename geometry_type_2> typename boost::enable_if< boost::mpl::and_<is_any_polygon_set_type<geometry_type_1>, is_any_polygon_set_type<geometry_type_2>, is_either_polygon_set_type<geometry_type_1, geometry_type_2> >, polygon_set_view<geometry_type_1, geometry_type_2, 3>
::type operator-(const geometry_type_1& lvalue, const geometry_type_2& rvalue) { return polygon_set_view<geometry_type_1, geometry_type_2, 3> (lvalue, rvalue); }
This won't work. In order to instantiate enable_if the compiler must instantiate both of its arguments. That means the compiler will try to instantiate polygon_set_view<> for every time the generic operator - in the stl is used for iterator arithmetic. Any error encountered trying to instantiate polygon_set_view will cause a syntax error and failed compilation even though the and_ evaluates to false.
Not true. Simply naming polygon_set_view<geometry_type_1, geometry_type_2, 3> doesn't force it to be instantiated. If you're getting an error from this, there must be some other reason. In Christ, Steven Watanabe

template <typename geometry_type_1, typename geometry_type_2> typename boost::enable_if< boost::mpl::and_<is_any_polygon_set_type<geometry_type_1>, is_any_polygon_set_type<geometry_type_2>, is_either_polygon_set_type<geometry_type_1, geometry_type_2> >, polygon_set_view<geometry_type_1, geometry_type_2, 3>
::type operator-(const geometry_type_1& lvalue, const geometry_type_2& rvalue) { return polygon_set_view<geometry_type_1, geometry_type_2, 3> (lvalue, rvalue); }
This won't work. In order to instantiate enable_if the compiler must >>instantiate both of its arguments. That means the compiler will try to >>instantiate polygon_set_view<> for every time the generic operator - in >>the stl is used for iterator arithmetic. Any error encountered trying to >>instantiate polygon_set_view will cause a syntax error and failed >>compilation even though the and_ evaluates to false.
Not true. Simply naming polygon_set_view<geometry_type_1, geometry_type_2, 3> doesn't force it to be instantiated. If you're getting an error from this, there must be some other reason.
You are right, there must be some other reason. I changed requires_1 to be identically boost::enable_if and it worked the way you said it would. There must be some non-intuitive difference between the way MSVC parses literal constant templates vs. templates on type for SFINAE purposes. My assumption was that SFINAE situations should parse the same whether the template where substitution fails is a template on a literal constant or on a type, which seems to be the case in gcc. Thank you for helping me with this, Luke

AMDG Simonson, Lucanus J wrote:
Not true. Simply naming polygon_set_view<geometry_type_1, geometry_type_2, 3> doesn't force it to be instantiated. If you're getting an error from this, there must be some other reason.
You are right, there must be some other reason. I changed requires_1 to be identically boost::enable_if and it worked the way you said it would. There must be some non-intuitive difference between the way MSVC parses literal constant templates vs. templates on type for SFINAE purposes. My assumption was that SFINAE situations should parse the same whether the template where substitution fails is a template on a literal constant or on a type, which seems to be the case in gcc.
The difference is that the for literal templates, the compiler does not even try to instantiate it. For SFINAE, the compiler has to instantiate it. Basically, the compiler only instantiates what it needs to. template<class T> typename boost::enable_if< boost::mpl::and_< a_metafunction<T>, // not instantiated yet another_metafunction<T> // not instantiated yet >, // not instantiated yet some_template<T> // not instantiated yet
::type // Now we get some template instantiation f(T);
enable_if must be instantiated to get at the ::type. enable_if in turn forces instantiation of and_ which forces instantiation of one or both of the metafunctions. In Christ, Steven Watanabe

The difference is that the for literal templates, the compiler does not even try to instantiate it. For SFINAE, the compiler has to instantiate >it. Basically, the compiler only instantiates what it needs to.
template<class T> typename boost::enable_if< boost::mpl::and_< a_metafunction<T>, // not instantiated yet another_metafunction<T> // not instantiated yet
, // not instantiated yet some_template<T> // not instantiated yet ::type // Now we get some template instantiation f(T);
enable_if must be instantiated to get at the ::type. enable_if in turn forces instantiation of and_ which forces instantiation of one or both of the metafunctions.
Maybe I shouldn't have used the word literal. It is a constant looked up by metafunction, of course. I was talking about different possible implementations for enable_if having different behaviors when compiled in MSVC. I don't know, but I would guess that the order of evaluation of template parameters separated by the comma operator is not defined by the standard? A syntax error in the instantiation of some_template<T> might lead the compilation to fail before trying to instantiate and_, I don't know. It seems that this is the case with MSVC some of the time. The boost implementation of enable_if works well with MSVC. The alternative 1: template <typename T, tyepname T2> struct enable_if_T { typedef T2 type; }; template <typename T2> struct enable_if_T<false_type, T2> {}; does not behave as expected (and as in gcc) when parsed by MSVC. The alternative 2: template <typename T, tyepname T2> struct enable_if_T {}; template <typename T2> struct enable_if_T<true_type, T2> { typedef T2 type; }; results in yet another behavior from MSVC, specifically the problem above, whereas alternative 1 resulted in ambiguous function syntax errors in many cases rather than the expected SFINAE overloading semantic. Boost's implementation of enable_if: template <typename T, typename T2> struct enable_if : enable_if_c<T::value, T2> {}; converts it from a template on type to a template on the constant specified by value. This seems to impact the order of template instantiation as well as the assumptions MSVC makes about whether SFINAE should apply. All three work equivalently in gcc (up to the point one would expect them to) from what I can observe. Is gcc being overly generous about what it allows to compile, or is MSVC falling short of what the standard says it should allow? Obviously, I wouldn't have these troubles if I had just used boost::enable_if from the outset, but I don't want to be given a fish, I want to learn to fish. Thanks again, Luke

AMDG Simonson, Lucanus J wrote:
Maybe I shouldn't have used the word literal. It is a constant looked up by metafunction, of course.
I was talking about different possible implementations for enable_if having different behaviors when compiled in MSVC.
I don't know, but I would guess that the order of evaluation of template parameters separated by the comma operator is not defined by the standard?
I believe not.
A syntax error in the instantiation of some_template<T> might lead the compilation to fail before trying to instantiate and_, I don't know. It seems that this is the case with MSVC some of the time.
The point is that the compiler doesn't know that some_template<T> needs to be instantiated until after it has gone through the SFINAE. I know that earlier versions of msvc would sometimes instantiate templates too soon, but I don't know
The boost implementation of enable_if works well with MSVC. The alternative 1:
template <typename T, tyepname T2> struct enable_if_T { typedef T2 type; }; template <typename T2> struct enable_if_T<false_type, T2> {};
does not behave as expected (and as in gcc) when parsed by MSVC.
The alternative 2:
template <typename T, tyepname T2> struct enable_if_T {}; template <typename T2> struct enable_if_T<true_type, T2> { typedef T2 type; };
results in yet another behavior from MSVC, specifically the problem above, whereas alternative 1 resulted in ambiguous function syntax errors in many cases rather than the expected SFINAE overloading semantic.
Boost's implementation of enable_if:
template <typename T, typename T2> struct enable_if : enable_if_c<T::value, T2> {};
converts it from a template on type to a template on the constant specified by value. This seems to impact the order of template instantiation as well as the assumptions MSVC makes about whether SFINAE should apply.
It's really a case of msvc taking SFINAE too far. If you only have a single function template, you can either get an ICE or bizarre runtime behavior. At the moment, I can't reproduce your problem, nor can I reproduce the ICE...
All three work equivalently in gcc (up to the point one would expect them to) from what I can observe. Is gcc being overly generous about what it allows to compile, or is MSVC falling short of what the standard says it should allow?
There is not doubt that this is an msvc compiler problem.
Obviously, I wouldn't have these troubles if I had just used boost::enable_if from the outset, but I don't want to be given a fish, I want to learn to fish.
As far as I can tell the only way to get sane behavior is to put enable_if at the top level. In the attached test, if USE_WRAP == 1 there will be either a compile error or an internal placeholder type will be printed at runtime. If USE_WRAP == 0, then everything works. The other flags seem to have no effect. In Christ, Steven Watanabe

-----Original Message----- From: boost-bounces@lists.boost.org [mailto:boost-bounces@lists.boost.org] On Behalf Of Steven Watanabe Sent: Monday, January 12, 2009 4:26 PM To: boost@lists.boost.org Subject: Re: [boost] MSVC9 SFINAE quirks? AMDG Simonson, Lucanus J wrote:
Maybe I shouldn't have used the word literal. It is a constant looked up by metafunction, of course.
I was talking about different possible implementations for enable_if having different behaviors when compiled in MSVC.
I don't know, but I would guess that the order of evaluation of template parameters separated by the comma operator is not defined by the standard?
I believe not.
A syntax error in the instantiation of some_template<T> might lead the compilation to fail before trying to instantiate and_, I don't know. It seems that this is the case with MSVC some of the time.
The point is that the compiler doesn't know that some_template<T> needs to be instantiated until after it has gone through the SFINAE. I know that earlier versions of msvc would sometimes instantiate templates too soon, but I don't know
The boost implementation of enable_if works well with MSVC. The alternative 1:
template <typename T, tyepname T2> struct enable_if_T { typedef T2 type; }; template <typename T2> struct enable_if_T<false_type, T2> {};
does not behave as expected (and as in gcc) when parsed by MSVC.
The alternative 2:
template <typename T, tyepname T2> struct enable_if_T {}; template <typename T2> struct enable_if_T<true_type, T2> { typedef T2 type; };
results in yet another behavior from MSVC, specifically the problem above, whereas alternative 1 resulted in ambiguous function syntax errors in many cases rather than the expected SFINAE overloading semantic.
Boost's implementation of enable_if:
template <typename T, typename T2> struct enable_if : enable_if_c<T::value, T2> {};
converts it from a template on type to a template on the constant specified by value. This seems to impact the order of template instantiation as well as the assumptions MSVC makes about whether SFINAE should apply.
Steven wrote:
It's really a case of msvc taking SFINAE too far. If you only have a single function template, you can either get an ICE or bizarre runtime behavior. At the moment, I can't reproduce your problem, nor can I reproduce the ICE...
Sorry, I should have given you something better to test with. The code snippet below is self contained and should reproduce the ambiguous function syntax error I saw. Undefining alternative1 makes the error go away, but for no clear reason because the other construct is logically equivalent for these purposes. I'm still working on reproducing the other strange compiler behavior I observed in a standalone test. Note that changing the get_first_name metafunction to directly specify steven and luke as the return types of the operators also makes the error go away, which is not clearly explainable or expected. #include <vector> struct true_type { static const bool value = true; }; struct false_type { static const bool value = false; }; #define alternative1 #ifdef alternative1 template <typename T, typename T2> struct enable_if_T {typedef T2 type; }; template <typename T2> struct enable_if_T<false_type, T2> {}; #else template <typename T, typename T2> struct enable_if_T {}; template <typename T2> struct enable_if_T<true_type, T2> { typedef T2 type; }; #endif template <typename T, typename T2> struct and_2 {}; template <> struct and_2<true_type, true_type> { typedef true_type type; }; struct luke {}; struct steven {}; struct simonson { typedef luke type; }; struct watanabe { typedef steven type; }; template <typename T> struct is_simonson { typedef false_type type; }; template <> struct is_simonson<simonson> { typedef true_type type; }; template <typename T> struct is_watanabe { typedef false_type type; }; template <> struct is_watanabe<watanabe> { typedef true_type type; }; template <typename T> struct get_first_name { typedef typename T::type type; }; template <typename T, typename T2> typename enable_if_T< typename and_2< typename is_watanabe<T>::type, typename is_watanabe<T2>::type >::type, typename get_first_name<T>::type
::type operator-(const T& lval, const T2& rval) { std::cout << "hello steven\n"; return steven(); }
template <typename T, typename T2> typename enable_if_T< typename and_2< typename is_simonson<T>::type, typename is_simonson<T2>::type >::type, typename get_first_name<T>::type
::type operator-(const T& lval, const T2& rval) { std::cout << "hello luke\n"; return luke(); }
#include <iostream> int main() { simonson s1, s2; watanabe w1, w2; std::vector<int> v(10); std::cout << v.size() << std::endl; s1 - s2; w1 - w2; return 0; } 1>c:\documents and settings\ljsimons\my documents\visual studio 2008\projects\msvc_bug2\msvc_bug2\msvc_bug2.cpp(76) : error C2593: 'operator -' is ambiguous 1> c:\documents and settings\ljsimons\my documents\visual studio 2008\projects\msvc_bug2\msvc_bug2\msvc_bug2.cpp(63): could be 'luke operator -<simonson,simonson>(const T &,const T2 &)' 1> with 1> [ 1> T=simonson, 1> T2=simonson 1> ] 1> c:\documents and settings\ljsimons\my documents\visual studio 2008\projects\msvc_bug2\msvc_bug2\msvc_bug2.cpp(50): or 'T2 operator -<simonson,simonson>(const T &,const T2 &)' 1> with 1> [ 1> T=simonson, 1> T2=simonson 1> ] 1> while trying to match the argument list '(simonson, simonson)' 1>c:\documents and settings\ljsimons\my documents\visual studio 2008\projects\msvc_bug2\msvc_bug2\msvc_bug2.cpp(77) : error C2593: 'operator -' is ambiguous 1> c:\documents and settings\ljsimons\my documents\visual studio 2008\projects\msvc_bug2\msvc_bug2\msvc_bug2.cpp(63): could be 'T2 operator -<watanabe,watanabe>(const T &,const T2 &)' 1> with 1> [ 1> T=watanabe, 1> T2=watanabe 1> ] 1> c:\documents and settings\ljsimons\my documents\visual studio 2008\projects\msvc_bug2\msvc_bug2\msvc_bug2.cpp(50): or 'steven operator -<watanabe,watanabe>(const T &,const T2 &)' 1> with 1> [ 1> T=watanabe, 1> T2=watanabe 1> ] 1> while trying to match the argument list '(watanabe, watanabe)' Regards, Luke
participants (2)
-
Simonson, Lucanus J
-
Steven Watanabe