Regression in Boost.Variant with gcc6.3 in C++03 mode
Hi, This is the simplest possible repro I could get: #include <boost/variant.hpp> class A {}; class B : private A {}; typedef boost::variant<A, B> variant_t; int main() { B b; variant_t v (b); } This used to work with Boost 1.59.0 but fails with 1.64.0 Using wandbox I've found that: - it started failing in 1.62.0 - clang does not have this problem - newer and older versions of gcc do have the same problem The error is miles long. I think the important parts are: boost/type_traits/is_convertible.hpp:149:53: error: 'A' is an inaccessible base of 'B' error: no matching function for call to 'boost::variant<A, B>::variant(B&)' It seems the metaprogramming messes up due to the private inheritance, and decides that the variant<A,B> is *not* constructible from B Workarounds are very welcome! Switching to C++14 is not an option (yet). Regards
On 27 Jun 2017, at 20:09, dariomt--- via Boost-users <boost-users@lists.boost.org> wrote:
#include <boost/variant.hpp> class A {}; class B : private A {}; typedef boost::variant<A, B> variant_t; int main() { B b; variant_t v (b); }
In `boost/type_traits/is_convertible.hpp`: ``` 123 struct any_conversion 124 { 125 template <typename T> any_conversion(const volatile T&); 126 template <typename T> any_conversion(const T&); 127 template <typename T> any_conversion(volatile T&); 128 template <typename T> any_conversion(T&); 129 }; 130 131 template <typename T> struct checker 132 { 133 static boost::type_traits::no_type _m_check(any_conversion ...); 134 static boost::type_traits::yes_type _m_check(T, int); 135 }; ``` And according to [https://stackoverflow.com/questions/30004771/why-is-a-malformed-function-use...], the standard conversion sequence takes precedence of the user-defined conversion (`any_conversion`), hence `checker<A>::_m_check(B(), 0)` results in a hard error. So I suspect this is a bug in 'type_traits' library, but I'm not sure. Hopefully somebody else can confirm. (I'm sorry but I don't know to whom should I CC this post.) An easy workaround would be specializing `boost::is_convertible` for your classes: ``` namespace boost { template <> struct is_convertible<B&, A> : boost::false_type {}; template <> struct is_convertible<const B&, A> : boost::false_type {}; } ``` I don't know why clang accepts your code though. Perhaps `boost::is_convertible` is conditionally compiled to some different implementations in Clang. ------ A reduced repo of the hard error in `boost::is_convertible` is: ``` struct any_conversion { template <typename T> any_conversion(const volatile T&); template <typename T> any_conversion(const T&); template <typename T> any_conversion(volatile T&); template <typename T> any_conversion(T&); }; typedef char (&yes_type)[2]; typedef char (&no_type)[1]; class A {}; class B : private A {}; static no_type _m_check(any_conversion ...); static yes_type _m_check(A, int); int main() { B b; _m_check(b, 0); } ``` Both GCC and Clang reject it.
2017-06-27 17:46 GMT+02:00 d25fe0be@outlook.com <d25fe0be@outlook.com>:
So I suspect this is a bug in 'type_traits' library, but I'm not sure. Hopefully somebody else can confirm. (I'm sorry but I don't know to whom should I CC this post.)
I don't know why clang accepts your code though. Perhaps `boost::is_convertible` is conditionally compiled to some different implementations in Clang.
Thanks for the analysis. My guess is that clang uses a different implementation of is_convertible. Also, I guess this works in C++14 because std::is_convertible is used instead? Regards
On 28 Jun 2017, at 00:53, dariomt@gmail.com wrote:
2017-06-27 17:46 GMT+02:00 d25fe0be@outlook.com <d25fe0be@outlook.com>: So I suspect this is a bug in 'type_traits' library, but I'm not sure. Hopefully somebody else can confirm. (I'm sorry but I don't know to whom should I CC this post.)
I don't know why clang accepts your code though. Perhaps `boost::is_convertible` is conditionally compiled to some different implementations in Clang.
Thanks for the analysis. My guess is that clang uses a different implementation of is_convertible. Also, I guess this works in C++14 because std::is_convertible is used instead?
Yes, after a deeper dig, `boost/type_traits/intrinsics.hpp` shows that `type_traits` uses Clang's intrinsics to do the job: ``` 157 #if defined(BOOST_CLANG) && defined(__has_feature) && !defined(__CUDACC__) [...] 213 # if __has_feature(is_convertible_to) 214 # define BOOST_IS_CONVERTIBLE(T,U) __is_convertible_to(T,U) 215 # endif ``` After commenting out macro `BOOST_IS_CONVERTIBLE`, clang errors out the original code as well. And I believe this finding gives us an easier workaround: Just defining our own `BOOST_IS_CONVERTIBLE` before including `boost/variant.hpp`, and the compilation error should disappear. ``` #include <type_traits> #define BOOST_IS_CONVERTIBLE(T,U) std::is_convertible<T, U>::value #include <boost/variant.hpp> [...] ``` As for the implementation of `boost::is_convertible` in C++14, although I agree with you that `type_traits` could (or should have?) use std::is_convertible when available, `boost/type_traits/is_convertible.hpp` shows that `type_traits` implemented their own C++11 version: ``` 53 // 54 55 namespace detail { 56 57 #if !defined(BOOST_NO_SFINAE_EXPR) && !defined(BOOST_NO_CXX11_RVALUE_REFERENCES) && !(defined(BOOST_GCC) && (BOOST_GCC < 40700)) 58 59 // This is a C++11 conforming version, place this first and use it wherever possible: 60 61 # define BOOST_TT_CXX11_IS_CONVERTIBLE 62 63 template <class A, class B, class C> 64 struct or_helper 65 { 66 static const bool value = (A::value || B::value || C::value); 67 }; 68 69 template<typename From, typename To, bool b = or_helper<boost::is_void<From>, boost::is_function<To>, boost::is_array<To> >::value> 70 struct is_convertible_basic_impl 71 { 72 // Nothing converts to function or array, but void converts to void: 73 static const bool value = is_void<To>::value; 74 }; 75 76 template<typename From, typename To> 77 class is_convertible_basic_impl<From, To, false> 78 { 79 typedef char one; ```
On 28 Jun 2017, at 01:27, d25fe0be@outlook.com wrote:
And I believe this finding gives us an easier workaround: Just defining our own `BOOST_IS_CONVERTIBLE` before including `boost/variant.hpp`, and the compilation error should disappear.
``` #include <type_traits> #define BOOST_IS_CONVERTIBLE(T,U) std::is_convertible<T, U>::value
#include <boost/variant.hpp> [...] ```
Oh sorry I forgot you're using C++03. Maybe you could write your own `is_convertible` in C++03 and redirect boost's to there.
On 28 Jun 2017, at 01:30, d25fe0be--- via Boost-users <boost-users@lists.boost.org> wrote:
On 28 Jun 2017, at 01:27, d25fe0be@outlook.com wrote:
And I believe this finding gives us an easier workaround: Just defining our own `BOOST_IS_CONVERTIBLE` before including `boost/variant.hpp`, and the compilation error should disappear.
``` #include <type_traits> #define BOOST_IS_CONVERTIBLE(T,U) std::is_convertible<T, U>::value
#include <boost/variant.hpp> [...] ```
Oh sorry I forgot you're using C++03.
Maybe you could write your own `is_convertible` in C++03 and redirect boost's to there.
Sorry I missed John's reply.. I believe John is right, without C++11 a fully conforming is_convertible seems not to be implementable (I didn't find a way to remove `_m_check` taking the inaccessible base as a parameter from the overload set.), hence 'your own `is_convertible`' seems not to be feasible either. Thank you John for pointing this out, and sorry for the noise.
2017-06-27 20:05 GMT+02:00 d25fe0be@outlook.com <d25fe0be@outlook.com>:
Sorry I missed John's reply..
I believe John is right, without C++11 a fully conforming is_convertible seems not to be implementable (I didn't find a way to remove `_m_check` taking the inaccessible base as a parameter from the overload set.), hence 'your own `is_convertible`' seems not to be feasible either.
Thank you John for pointing this out, and sorry for the noise.
Thanks for the follow up. So the only workaround with 1.64 is to get rid of the private inheritance, right? Anyway, my test case used to work in older versions of Boost. I'm not sure about what changed between 1.61 and 1.62, if it's a change in type_traits or a change in variant. Would it be possible to recover the lost functionality in 1.65?
On 28 Jun 2017, at 16:52, dariomt@gmail.com wrote:
2017-06-27 20:05 GMT+02:00 d25fe0be@outlook.com <d25fe0be@outlook.com>: Sorry I missed John's reply..
I believe John is right, without C++11 a fully conforming is_convertible seems not to be implementable (I didn't find a way to remove `_m_check` taking the inaccessible base as a parameter from the overload set.), hence 'your own `is_convertible`' seems not to be feasible either.
Thank you John for pointing this out, and sorry for the noise.
Thanks for the follow up.
So the only workaround with 1.64 is to get rid of the private inheritance, right?
I'm afraid yes.
Anyway, my test case used to work in older versions of Boost. I'm not sure about what changed between 1.61 and 1.62, if it's a change in type_traits or a change in variant. Would it be possible to recover the lost functionality in 1.65?
I think the change was introduced by commit [https://github.com/boostorg/variant/commit/b3650685f941a0c35cadfd878a185f274...], which tried to solve ticket #11602 [https://svn.boost.org/trac10/ticket/11602]. I don't know how can both #11602 and the issue here be solved at the same time. CC'ing Antony. Perhaps we can test for convertibility only if C++11 is available, and leave #11602 unsolved in C++03 (as there're still corner cases not solved as of now, see below)? --- As `boost::is_constructible` triggers a hard error when testing a class and its privately parent, the following code fails the compilation as well (both before (because of #11602) and after (because of the hard error triggered by `boost::is_convertible`) commit b3650685f941a0c35cadfd878a185f274e132788): ``` #include <boost/variant.hpp> class A {}; class B : private A {}; void foo(boost::variant<A> v) {} void foo(boost::variant<B> v) {} int main() { B b; foo(b); } ```
So I suspect this is a bug in 'type_traits' library, but I'm not sure. Hopefully somebody else can confirm. (I'm sorry but I don't know to whom should I CC this post.)
The short answer, is that a fully conforming is_convertible requires C++11 language features, so your test code is fine in C++11 or 14 mode but in 03 mode uses legacy code which is known not to work in every circumstance. clang works because the clang front end provides an intrinsic for is_convertible which gcc does not (last I checked). Of course that doesn't help you much, sorry, John. --- This email has been checked for viruses by AVG. http://www.avg.com
participants (3)
-
d25fe0be@outlook.com
-
dariomt@gmail.com
-
John Maddock