[concept_check] Problem with concept checks and overloads

Hello, Barend and I have been facing a problem while integrating concept checks into the Geometry Library. In order to simplify the problem, I reduced it to a very simple test case: template <typename T> struct foo {}; template <class X> struct DummyConcept { typedef typename X::something something; }; template<typename T> BOOST_CONCEPT_REQUIRES(((DummyConcept<T>)), (void)) func(T&) { std::cout << "first overload" << std::endl; } template<typename T> void func(foo<T>) { std::cout << "second overload" << std::endl; } int main() { func(foo<int>()); return 0; } This code doesn't compile because the compiler tries to check foo<int> against the DummyConcept, even though it's supposed to select the second overload, which doesn't carry any concept requirement. Removing the check makes it compile and a run displays "second overload". If I put back the check and add a "typedef void something" into foo, it compiles and displays the same thing. So I deduce that, more generally, a compiler compiles the return type of every candidate overload before selecting the right one, and that's why we have this issue with concept checking. So I have 2 questions: - would there be any way to differ the check until after the right overload has been selected, thus doing the check only for that overload? - if no, is there a better workaround than replacing BOOST_CONCEPT_REQUIRES by a simple BOOST_ASSERT inside the function? Thanks in advance. Bruno

Bruno Lalande wrote: > Hello, > > Barend and I have been facing a problem while integrating concept > checks into the Geometry Library. In order to simplify the problem, I > reduced it to a very simple test case: I tested your code on two compilers and found this: 1. It works perfectly on msvc 2. It fails in mingw g++ 4.3.0 I think the implementation uses SFINAE like enable_if does, but mingw g++ 4.3.0 is calling that an error. I think this has to be a compiler specific bug (since it works differently on the two compilers) --John

Hi John, Thanks to have taken the time to test my code. You had the same result as Barend, who was able to reproduce the problem with GCC cygwin (3.4.4) but not MSVC. But the strange thing is that for me, the problem occurs for any compiler (MSVC or GCC). I really don't understand. Here is the list of compilers tested: - Barend: MSVC 8 Express (succeeds), cygwin GCC 3.4.4 (fails) - Me: MSVC 8 (fails), MSVC 9 Express (fails), GCC 4.2.3 (fails) I don't think there can be any functional difference between the express and normal version of the same MSVC so I don't understand. Could you precise the exact version you used? I've attached my MSVC compilation results to the mail. Bruno

Please note that the files attached to my previous mail are misnamed => increment the number in each of them. Bruno

Bruno Lalande wrote:
Hi John,
Thanks to have taken the time to test my code. You had the same result as Barend, who was able to reproduce the problem with GCC cygwin (3.4.4) but not MSVC. But the strange thing is that for me, the problem occurs for any compiler (MSVC or GCC). I really don't understand. Here is the list of compilers tested: - Barend: MSVC 8 Express (succeeds), cygwin GCC 3.4.4 (fails) - Me: MSVC 8 (fails), MSVC 9 Express (fails), GCC 4.2.3 (fails)
I don't think there can be any functional difference between the express and normal version of the same MSVC so I don't understand. Could you precise the exact version you used?
I've attached my MSVC compilation results to the mail.
Bruno The code in the email did not have headers, so I added
#include <iostream> #include <boost/concept_check.hpp> #include <boost/concept/requires.hpp> Then I tested with Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.762 for 80x86 (that is the professional version) It worked. As I said before, mingw-gcc 4.3.0 fails, but it looks like that is the norm for gcc. I think boost is using the fact that the compiler should fail to define a nested enum when a concept assert fails, but that SFINAE should not consider that an error. It looks like it is doing this so that it can chain together many concept checks. I think this must be a bug, so I added a ticket http://svn.boost.org/trac/boost/ticket/2137. The exact sourcecode I used is attached. It looks to me like in boost/concept_check/where_fail.cpp they are not testing *why* the concept check fails. I think in g++ it should be saying something like where_fail.cpp:11: error: no matching function for call to 'sort(std::_List_iterator<int>, std::_List_iterator<int>)' But instead I TONS of other errors.

John C. Femiani wrote:
Bruno Lalande wrote:
Hi John,
Thanks to have taken the time to test my code. You had the same result as Barend, who was able to reproduce the problem with GCC cygwin (3.4.4) but not MSVC. But the strange thing is that for me, the problem occurs for any compiler (MSVC or GCC). I really don't understand. Here is the list of compilers tested: - Barend: MSVC 8 Express (succeeds), cygwin GCC 3.4.4 (fails) - Me: MSVC 8 (fails), MSVC 9 Express (fails), GCC 4.2.3 (fails)
I don't think there can be any functional difference between the express and normal version of the same MSVC so I don't understand. Could you precise the exact version you used?
I've attached my MSVC compilation results to the mail.
Bruno
I think boost is using the fact that the compiler should fail to define a nested enum when a concept assert fails, but that SFINAE should not consider that an error. It looks like it is doing this so that it can chain together many concept checks. I think this must be a bug, so I added a ticket http://svn.boost.org/trac/boost/ticket/2137.
OK, so apparently the ticket was invalid because BOOST_CONCEPT_REQUIRES does not actually use SFINAE, for technical reasons, so if we want concept based overloading it just wont work. I think that in order to approximate that you will need to create a metafunction template<class T> struct is_dummy: ::boost::mpl::false_ {}; template<template<class T> > struct is_dummy<foo<T> > : ::boost::mpl::true_ { BOOST_CONCEPT_ASSERT( (DummyConcept<T> )); }; template<typename T> typename ::boost::enable_if<is_dummy<T>, void>::type func(T) { std::cout << "first overload" << std::endl; } and make that part of the concept, so you can use it with enable_if. --John

OK, thanks for having filed that ticket and thanks to Dave for the explanation. Actually, I didn't have realized that my problem was related to SFINAE, because my intent is not to do SFINAE and concept-based overload selection. I don't want the compiler to select the second overload *because* the check fails on the first one. I want it to be able to select the second overload *event though* the check fails on the first one, just as it does in absence of any check. But now I realize that requesting that the compiler doesn't try to compile the return type of an overload until it has actually decided to select it would mean, precisely, disabling the SFINAE mechanism. So we will follow Dave's advice to use BOOST_CONCEPT_ASSERT instead. I still don't understand why I'm the only one to reproduce that under MSVC. My msvc-8 version is 14.00.50727.42... Thanks for the help Bruno

Bruno Lalande wrote:
I still don't understand why I'm the only one to reproduce that under MSVC. My msvc-8 version is 14.00.50727.42... I tried again, starting from scratch, this time I get msvc errors too (go figure). I don't know what I did differently.
--John

Bruno wrote:
So I deduce that, more generally, a compiler compiles the return type of every candidate overload before selecting the right one, and that's why we have this issue with concept checking. So I have 2 questions: - would there be any way to differ the check until after the right overload has been selected, thus doing the check only for that overload? - if no, is there a better workaround than replacing BOOST_CONCEPT_REQUIRES by a simple BOOST_ASSERT inside the function?
I ran into a problem with the flexibility of SFNAE where indirection in the substitution failure resulted in an error. template <typename T> struct metafunction { typedef typename T::some_type type; }; struct A {}; //A does not define some_type template <typename T> metafunction<A>::type foo(T t) {} //intended SFNAE protected function template <typename T> void foo(T t) {} int main() { foo(A()); return 0; }
gcc/4.3.0/g++ -g -Wall test8.cpp test8.cpp: In instantiation of ?metafunction<A>?: test8.cpp:8: instantiated from here test8.cpp:3: error: no type named ?some_type? in ?struct A?
And from this behavior of the compiler I inferred that substitution failure is not an error only in a direct sense. It succeeded in substituting A in metafunction because metafunction defines type for all T, but because A doesn't define some_type it is a syntax error, not substitution failure at that point in compilation. This seems to be similar to what you wrote and the problem you saw. I work around the issue in my library by making structs that I intended to use for SFNAE empty by default and specialize them for each type. It would be convenient if BOOST_CONCEPT_REQUIRES somehow worked around this limitation and allowed me to specify a default expectation for a type that can substitute in a metafunction so that only those that don't model the concept as expected need a concept mapping specialization of the metafunction and still provide SFNAE behavior. I don't know if that is possible. Right now I have a generic concept that encompasses all geometric types: template <typename T> struct geometry_concept {}; This is a metafunction which I use to lookup the conceptual type of a given type and I specialize it for each data type like so: template <> struct geometry_concept<int> { typedef coordinate_concept type; }; template <typename T> struct geometry_concept<rectangle_data<T> > { typedef rectangle_concept type; }; This registers each type with the library so I know what to do with it. By doing it this way I am able to use the geometry_concept metafunction for SFNAE, but if the default looks like: template <typename T> struct geometry_concept { typedef typename T::concept_type type; } the compiler then gives me syntax errors instead of SFNAE behavior. I would prefer not to have to register types that implement the default expectation for their behavior, but couldn't see a way around it. It looks like the concept check is able to give you a syntax error in the case that the given type doesn't model the concept, but it does not also provide SFNAE to allow you to define another generic function of the same name to handle such a type. Whether that is a compiler bug or not, I don't know, but we need to write code that works with the current generation of compilers. Bruno's problem still isn't solved. He can get SFNAE by making DummyConcept empty by default and specialize it for each type that models the concept, but that isn't really satisfactory. In my case, my expectation for user types is that they will always require a concept map because I assume they were authored prior to my library, so for me requiring registration and concept mapping specializations of traits classes is not an undue burden. Particularly since specializations can themselves be templates, and can even inherit from other specializations, providing the required specializations is minimal effort. BTW, I have finished the complete design and implementation for generic operator syntax for Boolean geometric operations in my library, and if there is interest I'd be happy the share the design with the list. I allow syntax such as: std::vector<RectangleType> rects1, rects2, rects3; rects1 & rects2 & rects3; Where operator& is (SFNAE safely) generic and implements a boolean AND operation on the 2D geometry of the arguments with operator templates. The requirement is that the types used be registered with the library and have the appropriate traits defined. Currently I have a 30 page powerpoint document that describes the API design and implementation. It is text only with lots of code examples, so I could make it into one very long email and send it to the boost list for feedback. The up-to-date code is also checked into the sandbox under gtl. It compiles under gcc 3.2.2 3.4.2, 4.2.0 and 4.3.0 as well as recent icc versions. I have not yet tried it on windows. Luke

Hi Luke, I'm not sure your problem is the same as mine. My problem is that the result type gets compiled even though there's absolutely no ambiguity between the 2 functions. There's no need to do any SFINAE discrimination to conclude that foo<T>& matches much better that T& for this call. I'd be happy if the compiler waited to have a true ambiguity to solve before compiling any return type but things are like that, I can't rewrite the norm. Your problem is rather a lack of strength of SFINAE. As you say, the SFINAE principle seems to be only applied on a first-level check. If the substitution fails at a more nested level, it's an error. To be honest, this surprises me. Don't know what the norm says about that... Thanks John to have retried your test, I feel less alone now :-) Bruno

Bruno wrote:
I'm not sure your problem is the same as mine. My problem is that the result type gets compiled even though there's absolutely no ambiguity between the 2 functions. There's no need to do any SFINAE discrimination to conclude that foo<T>& matches much better that T& for this call. I'd be happy if the compiler waited to have a true ambiguity to solve before compiling any return type but things are like that, I can't rewrite the norm.
Your problem is rather a lack of strength of SFINAE. As you say, the SFINAE principle seems to be only applied on a first-level check. If the substitution fails at a more nested level, it's an error. To be honest, this surprises me. Don't know what the norm says about that...
Yes, I realized that as I read the rest of your thread. I assumed you were asking why SFINAE didn't prevent the compiler error, as did a couple other people at first, because I had just finished some outside the box programming to deal with the limitation of SFINAE not allowing me to specify default behavior for my metafunctions. I was surprised by the solution I came up with, and I'd like to share it with you. My goal, if you recall, was to provide generic +, &, |, ^, *, -, +=, &=, |=, *=, &= and -= operators for performing 2D geometric set operations. The arguments that these operators should take could be any stl container of objects that model an applicable geometry concept, my own data structure that encapsulates the input to the algorithm, or the operator template type that the operators return. The declaration for the operator+ looks like so: template <typename geometry_type_1, typename geometry_type_2> polygon_set_view< typename polygon_set_traits<geometry_type_1>::operator_arg_type, typename polygon_set_traits<geometry_type_2>::operator_arg_type, boolean_op::BinaryOr> operator+(const geometry_type_1& lvalue, const geometry_type_2& rvalue) In the declaration of the operator I'm depending on SFINAE to prevent the template from being instantiated for any type T where the polygon_set_traits<T> does not declare an operator_arg_type. Because substitution must fail in the first level check I cannot declare a default polygon_set_traits and have to instead specialize the polygon_set_traits struct for every valid argument type. For my own types, such as the operator template I can use a templated specialization: template <typename T> struct polygon_set_traits {}; template <typename T1, typename T2, typename T3> struct polygon_set_traits<polygon_set_view<T1, T2, T3> > { typedef polygon_set_view<T1, T2, T3> operator_arg_type; ... }; The specialization includes several additional typedefs and functions that define the minimal sufficient interface to use the type as a polygon set for the purposes of polygon set operations. It is about 30 lines of code to specialize the polygon_set_traits for each type. I needed to specialize it for any container of geometry objects, but because it would break down my SFINAE protection I can't specialize it for container of any element type: template <typename element_type> struct polygon_set_traits<std::vector<element_type> > { ... }; because that would allow any vector to instantiate the operator leading to syntax errors in the case were a correct generic operator for a vector of that type existed in some other library. That means I have to specialize polygon_set_traits for each container of each geometry type explicitly. However, because containers conform to a standard set of behaviors, and my geometry concepts allow me to treat different types of geometry objects generically the specialization for each such container can be made to look identical to that of any other. I didn't want that code duplication, but how could I avoid it without templating the element type? A macro? I found a better way. I created a wrapper class that wraps a container of geometry objects: template <typename T> class polygon_set_wrapper { private: T& t_; ... }; and created a generic specialization of the polygon set traits for the wrapper: template <typename T> struct polygon_set_traits<polygon_set_wrapper<T> > { typedef T operator_arg_type; ... }; That implemented all the required traits for a generic container of geometry objects such that it worked for any container of geometry objects. I then inherit the explicit specialization of polygon_set_traits for specific containers of geometry objects from that one like so: template <typename T> struct polygon_set_traits<std::vector<rectangle_data<T> > > : public polygon_set_traits<polygon_set_wrapper<std::vector<rectangle_data<T> > >
{};
which allows me to specify a generic category of traits and assign it to specific types using inheritance of specialization to meet the requirement that the template used for SFINAE is narrowly defined and substitution fails at the first level of compilation. I found this use of inheritance between specializations of a class/struct surprising in the sense that it is both supported and useful. I thought other people on the list might find it interesting. Luke
participants (3)
-
Bruno Lalande
-
John C. Femiani
-
Simonson, Lucanus J