Using boost::enable_if with template copy constructors

Hi, Can anyone explain why the following won't compile (using g++ 3.3.2 under Linux): #include <boost/utility/enable_if.hpp> #include <boost/type_traits/is_convertible.hpp> using namespace std; struct Foo { Foo() {} template <typename T> Foo(T const & src, typename boost::enable_if< boost::is_convertible<T,Foo>, void >::type * dummy = 0) { int i = 1; // filler. } template <typename T> Foo(T const & src, typename boost::disable_if< boost::is_convertible<T,Foo>, void >::type * dummy = 0) { int i = 2; // ditto. } }; int main() { Foo f; Foo f2(f); return 0; }; When I attempt to compile this code with g++ 3.3.2, I get the following error messages: /boost/boost/type_traits/is_convertible.hpp: In instantiation of `boost::disable_if<boost::is_convertible<Foo, Foo>, void>': /boost/boost/type_traits/is_convertible.hpp:128: instantiated from `boost::detail::is_convertible_basic_impl<Foo&, Foo>' /boost/boost/type_traits/is_convertible.hpp:228: instantiated from `boost::detail::is_convertible_impl<Foo, Foo>' test.cpp:36: instantiated from `boost::detail::is_convertible_impl_dispatch<Foo, Foo>' test.cpp:36: instantiated from `boost::is_convertible<Foo, Foo>' test.cpp:36: instantiated from `boost::enable_if<boost::is_convertible<Foo, Foo>, void>' test.cpp:36: instantiated from here /boost/boost/type_traits/is_convertible.hpp:128: error: `value' is not a member of type `boost::is_convertible<Foo, Foo>' /boost/boost/type_traits/is_convertible.hpp: In instantiation of `boost::detail::is_convertible_basic_impl<Foo&, Foo>': /boost/boost/type_traits/is_convertible.hpp:228: instantiated from `boost::detail::is_convertible_impl<Foo, Foo>' test.cpp:36: instantiated from `boost::detail::is_convertible_impl_dispatch<Foo, Foo>' test.cpp:36: instantiated from `boost::is_convertible<Foo, Foo>' test.cpp:36: instantiated from `boost::enable_if<boost::is_convertible<Foo, Foo>, void>' test.cpp:36: instantiated from here /boost/boost/type_traits/is_convertible.hpp:128: error: initializing argument 1 of `static yes_type boost::detail::checker<T>::_m_check(T, int) [with T = Foo]' If I use boost::is_same<> in place of boost::is_convertible<>, everything is fine. Is there something that I'm missing, or is this a bug? Thanks in advance, Tim

Tim Robertson <timr@u.washington.edu> writes:
Hi,
Can anyone explain why the following won't compile (using g++ 3.3.2 under Linux):
#include <boost/utility/enable_if.hpp> #include <boost/type_traits/is_convertible.hpp>
using namespace std;
struct Foo { Foo() {}
template <typename T> Foo(T const & src, typename boost::enable_if< boost::is_convertible<T,Foo>, void >::type * dummy = 0) { int i = 1; // filler. }
template <typename T> Foo(T const & src, typename boost::disable_if< boost::is_convertible<T,Foo>, void >::type * dummy = 0) { int i = 2; // ditto. }
};
int main() { Foo f; Foo f2(f);
return 0; };
When I attempt to compile this code with g++ 3.3.2, I get the following error messages:
Well, first of all, neither of those two constructors is a copy ctor (a copy ctor has the signature Foo(Foo <cv>&) -- no templates), so the compiler should be instantiating the implicitly-generated copy ctor, and, I think, accept your code without complaint. But that's probably not what you meant for it to do. In this case it looks like you have a kind of circular dependency problem. You can actually learn a lot by reading the error messages. Reversing your instantiation backtrace, I get:
test.cpp:36: instantiated from here
I assume that's the 2nd line of main()
test.cpp:36: instantiated from `boost::enable_if<boost::is_convertible<Foo, Foo>, void>'
It's trying to decide if the first ctor is a match
test.cpp:36: instantiated from `boost::is_convertible<Foo, Foo>'
To do that it needs to decide the value nested in is_convertible<Foo,Foo>.
test.cpp:36: instantiated from `boost::detail::is_convertible_impl_dispatch<Foo, Foo>' /boost/boost/type_traits/is_convertible.hpp:228: instantiated from `boost::detail::is_convertible_impl<Foo, Foo>' /boost/boost/type_traits/is_convertible.hpp:128: instantiated from `boost::detail::is_convertible_basic_impl<Foo&, Foo>'
Inside implementation details of is_convertible:
/boost/boost/type_traits/is_convertible.hpp: In instantiation of `boost::disable_if<boost::is_convertible<Foo, Foo>, void>':
To determine convertibility it has to check out the ctors and see if there's one that implicitly converts Foo->Foo
/boost/boost/type_traits/is_convertible.hpp:128: error: `value' is not a member of type `boost::is_convertible<Foo, Foo>'
In evaluating the applicability of the first ctor, it needs to know the value nested in is_convertible<Foo,Foo>. We've been here before! HTH, -- Dave Abrahams Boost Consulting www.boost-consulting.com

On Sep 6, 2006, at 12:19 AM, David Abrahams wrote:
But that's probably not what you meant for it to do. In this case it looks like you have a kind of circular dependency problem. You can actually learn a lot by reading the error messages. Reversing your instantiation backtrace, I get:
test.cpp:36: instantiated from here
I assume that's the 2nd line of main()
test.cpp:36: instantiated from `boost::enable_if<boost::is_convertible<Foo, Foo>, void>'
It's trying to decide if the first ctor is a match
test.cpp:36: instantiated from `boost::is_convertible<Foo, Foo>'
Yes, everything up to this point was straightforward. The compiler is trying to choose between the constructors by instantiating the various enable_if templates. Unfortunately, I'm not as familiar with the internals of enable_if<> as many of you, and the *next* part was a bit confusing...
To do that it needs to decide the value nested in is_convertible<Foo,Foo>.
test.cpp:36: instantiated from `boost::detail::is_convertible_impl_dispatch<Foo, Foo>' /boost/boost/type_traits/is_convertible.hpp:228: instantiated from `boost::detail::is_convertible_impl<Foo, Foo>' /boost/boost/type_traits/is_convertible.hpp:128: instantiated from `boost::detail::is_convertible_basic_impl<Foo&, Foo>'
Inside implementation details of is_convertible:
/boost/boost/type_traits/is_convertible.hpp: In instantiation of `boost::disable_if<boost::is_convertible<Foo, Foo>, void>':
To determine convertibility it has to check out the ctors and see if there's one that implicitly converts Foo->Foo
...but it can't do that if the constructors that would perform the conversion are themselves enabled/disabled with enable_if?
/boost/boost/type_traits/is_convertible.hpp:128: error: `value' is not a member of type `boost::is_convertible<Foo, Foo>'
In evaluating the applicability of the first ctor, it needs to know the value nested in is_convertible<Foo,Foo>. We've been here before!
Right. So again, it seems as though it isn't possible to do what I'm trying to do. The enable_if<> template requires the use of the class' copy constructor. Even if a default copy constructor is provided, the compiler still wants to obtain the nested value type in is_convertible<>, and it can't do that, because it needs to decide between the three copy-constructor-like functions in the class, two of which depend on enable_if<> to determine their signatures. Is that the correct way to interpret this situation? In retrospect, this was probably too trivial an example to give you -- I realize that it's silly to want to use enable_if<> to do what the compiler can do for itself. There's a far more complex situation behind the question, but I'm having trouble distilling it down to a question of reasonable length.... -Tim

I worked up a much better example of what I'm trying to do: /////////////////////////////////////////////////////////////////////// ////////////////////////////// #include <iostream> #include <boost/utility/enable_if.hpp> #include <boost/type_traits/is_convertible.hpp> using namespace std; template <typename T> struct MyValueType { typedef MyValueType<T> self_type; MyValueType() {} MyValueType(self_type const & v) {} template <typename V> MyValueType(V const & v, typename boost::enable_if< boost::is_convertible<V,self_type> >::type * dummy = 0) { cout << "MyValueType template constructor 1" << endl; } template <typename V> MyValueType(V const & v, typename boost::disable_if< boost::is_convertible<V,self_type> >::type * dummy = 0) { cout << "MyValueType template constructor 2" << endl; } }; template <typename T> struct MyContainer { typedef T value_type; typedef MyContainer<T> self_type; MyContainer() {} MyContainer(MyContainer const & c) {} template <typename C> MyContainer(C const & c, typename boost::enable_if< boost::is_convertible<typename C::value_type, value_type> >::type * dummy = 0) { cout << "MyContainer template constructor 1" << endl; } template <typename C> MyContainer(C const & c, typename boost::disable_if< boost::is_convertible<typename C::value_type, value_type> >::type * dummy = 0) { cout << "MyContainer template constructor 2" << endl; } }; typedef MyValueType<int> integer_value; typedef MyValueType<double> real_value; typedef MyContainer<integer_value> integer_container; typedef MyContainer<real_value> real_container; int main() { integer_container ic; real_container rc(ic); return 0; }; /////////////////////////////////////////////////////////////////////// ////////////////////////////// In this case, compiling with the first set of typedefs results in the following errors: /boost/boost/utility/enable_if.hpp: In instantiation of 'boost::disable_if<boost::is_convertible<integer_value, MyValueType<double> >, void>': boost/boost/type_traits/is_convertible.hpp:128: instantiated from 'boost::detail::is_convertible_basic_impl<integer_value&, MyValueType<double> >' /boost/boost/type_traits/is_convertible.hpp:228: instantiated from 'boost::detail::is_convertible_impl<integer_value, MyValueType<double>
'
/boost/boost/type_traits/is_convertible.hpp:302: instantiated from 'boost::detail::is_convertible_impl_dispatch<integer_value, MyValueType<double> >' /boost/boost/type_traits/is_convertible.hpp:348: instantiated from 'boost::is_convertible<integer_value, MyValueType<double> >' /boost/boost/utility/enable_if.hpp:36: instantiated from 'boost::enable_if<boost::is_convertible<integer_value, MyValueType<double> >, void>' test.cpp:72: instantiated from here (**This is the call to the copy constructor in main()**) /boost/boost/utility/enable_if.hpp:59: error: incomplete type 'boost::is_convertible<integer_value, MyValueType<double> >' used in nested name specifier From what I can tell, this looks like the same problem that I was having in the much simpler example -- the compiler wants to instantiate is_convertible<integer_value,real_value>, and in order to do this, it needs the constructors in MyValueType, which are themselves enabled/disabled with enable_if<> and disable_if<>. Is there a way that I can make this work without having to resort to using is_same<> (which works, but doesn't capture the sematics that I need)? Thanks again, Tim On Sep 6, 2006, at 1:13 PM, Tim Robertson wrote:
In retrospect, this was probably too trivial an example to give you -- I realize that it's silly to want to use enable_if<> to do what the compiler can do for itself. There's a far more complex situation behind the question, but I'm having trouble distilling it down to a question of reasonable length...

Tim Robertson <timr@u.washington.edu> writes:
On Sep 6, 2006, at 12:19 AM, David Abrahams wrote:
But that's probably not what you meant for it to do. In this case it looks like you have a kind of circular dependency problem. You can actually learn a lot by reading the error messages. Reversing your instantiation backtrace, I get:
test.cpp:36: instantiated from here
I assume that's the 2nd line of main()
test.cpp:36: instantiated from `boost::enable_if<boost::is_convertible<Foo, Foo>, void>'
It's trying to decide if the first ctor is a match
test.cpp:36: instantiated from `boost::is_convertible<Foo, Foo>'
Yes, everything up to this point was straightforward. The compiler is trying to choose between the constructors by instantiating the various enable_if templates. Unfortunately, I'm not as familiar with the internals of enable_if<> as many of you, and the *next* part was a bit confusing...
To do that it needs to decide the value nested in is_convertible<Foo,Foo>.
Note---^^^^^^^^^^^^^^^^^^^^^^^
test.cpp:36: instantiated from `boost::detail::is_convertible_impl_dispatch<Foo, Foo>' /boost/boost/type_traits/is_convertible.hpp:228: instantiated from `boost::detail::is_convertible_impl<Foo, Foo>' /boost/boost/type_traits/is_convertible.hpp:128: instantiated from `boost::detail::is_convertible_basic_impl<Foo&, Foo>'
Inside implementation details of is_convertible:
/boost/boost/type_traits/is_convertible.hpp: In instantiation of `boost::disable_if<boost::is_convertible<Foo, Foo>, void>':
To determine convertibility it has to check out the ctors and see if there's one that implicitly converts Foo->Foo
...but it can't do that if the constructors that would perform the conversion are themselves enabled/disabled with enable_if?
Almost but, not quite. It's the specific criterion on whose basis they're enabled/disabled: is Foo convertible to Foo? The problem is that is_convertible<Foo,Foo>::value is not yet known, and (aside from the fact that there's an implicitly generated copy ctor that's a perfect match which the compiler seems to ignore at this stage, of course) without that ::value the compiler can't decide whether either of those constructors is a match for a Foo argument. In fact, we're only checking for that match because we want to know that very ::value!
/boost/boost/type_traits/is_convertible.hpp:128: error: `value' is not a member of type `boost::is_convertible<Foo, Foo>'
In evaluating the applicability of the first ctor, it needs to know the value nested in is_convertible<Foo,Foo>. We've been here before!
Right. So again, it seems as though it isn't possible to do what I'm trying to do.
Sure it is. You could use enable_if< mpl::or_<is_same<T,Foo>, is_convertible<T,Foo> >, ... > Type sameness can be determined without looking at constructors or even knowing anything about the definition of the type.
The enable_if<> template requires the use of the class' copy constructor. Even if a default copy constructor is provided, the compiler still wants to obtain the nested value type in is_convertible<>, and it can't do that, because it needs to decide between the three copy-constructor-like functions in the class, two of which depend on enable_if<> to determine their signatures. Is that the correct way to interpret this situation?
Sorta. -- Dave Abrahams Boost Consulting www.boost-consulting.com

On Sep 6, 2006, at 4:15 PM, David Abrahams wrote:
The problem is that is_convertible<Foo,Foo>::value is not yet known, and (aside from the fact that there's an implicitly generated copy ctor that's a perfect match which the compiler seems to ignore at this stage, of course)
Consistently so, for both g++3.x and g++4.x. (Am I missing something, or is this not sufficiently weird to register as a problem?)
without that ::value the compiler can't decide whether either of those constructors is a match for a Foo argument. In fact, we're only checking for that match because we want to know that very ::value!
Got it. The compiler can't instantiate is_convertible<Foo,Foo> if the constructor(s) that enable the conversion (from Foo to Foo) themselves have signatures that depend on is_convertible<Foo,Foo>.
Right. So again, it seems as though it isn't possible to do what I'm trying to do.
Sure it is. You could use
enable_if< mpl::or_<is_same<T,Foo>, is_convertible<T,Foo> >, ... >
Type sameness can be determined without looking at constructors or even knowing anything about the definition of the type.
I see...skip the is_convertible<> check when the types are actually the same, thus avoiding the instantiation of is_convertible<Foo,Foo>. This might work for my real problem. I'll give it a shot. Thanks, Tim

Tim Robertson <timr@u.washington.edu> writes:
On Sep 6, 2006, at 4:15 PM, David Abrahams wrote:
The problem is that is_convertible<Foo,Foo>::value is not yet known, and (aside from the fact that there's an implicitly generated copy ctor that's a perfect match which the compiler seems to ignore at this stage, of course)
Consistently so, for both g++3.x and g++4.x.
(Am I missing something, or is this not sufficiently weird to register as a problem?)
Depends on your definition of "problem." It might be a QOI, rather than a conformance, issue. But go ahead and report it anyway -- can't hurt. :) -- Dave Abrahams Boost Consulting www.boost-consulting.com

On Sep 6, 2006, at 8:24 PM, David Abrahams wrote:
The problem is that is_convertible<Foo,Foo>::value is not yet known, and (aside from the fact that there's an implicitly generated copy ctor that's a perfect match which the compiler seems to ignore at this stage, of course)
Consistently so, for both g++3.x and g++4.x.
(Am I missing something, or is this not sufficiently weird to register as a problem?)
Depends on your definition of "problem." It might be a QOI, rather than a conformance, issue. But go ahead and report it anyway -- can't hurt. :)
Actually...I think this is a bug in the is_convertible<> implementation. Tonight, I wrote two versions of the test program -- one that uses boost::is_convertible<>, another that uses my own implementation of the convertibility test (and my own implementation of enable_if<>). My implementation compiles and works as expected (g++ 4.0.0), whereas the boost::enable_if<> version fails to compile. I'm going to try to attach both pieces of test code, so that they come through correctly. If you would like me to send these directly to you, or report a bug, let me know. -tim

Tim Robertson <timr@u.washington.edu> writes:
Actually...I think this is a bug in the is_convertible<> implementation.
Tonight, I wrote two versions of the test program -- one that uses boost::is_convertible<>, another that uses my own implementation of the convertibility test (and my own implementation of enable_if<>). My implementation compiles and works as expected (g++ 4.0.0), whereas the boost::enable_if<> version fails to compile.
I'm going to try to attach both pieces of test code, so that they come through correctly. If you would like me to send these directly to you, or report a bug, let me know.
I think you'd better check CVS first: ---------------------------- revision 1.35 date: 2005/11/12 17:28:37; author: johnmaddock; state: Exp; lines: +17 -13 Make is_base_of<T,T>::value true for all types T, as per TR1. Make is_convertible<T, void>::value true for all types T, as per TR1. -- Dave Abrahams Boost Consulting www.boost-consulting.com
participants (2)
-
David Abrahams
-
Tim Robertson