[MPL] HAS_XXX gives the wrong answer after "enable_if" on incomplete type in GCC 4.6/7
I've run into an unexpected behaviour, and I'm not sure if the compiler has a bug, if Boost.MPL has a bug, or if I have the wrong expectations. I am using Boost v1.49.0, but I also tested r82084 of the Boost trunk. In particular, I have: BOOST_MPL_HAS_XXX_TRAIT_DEF(trait) // skipping important code here struct traitor_1 { typedef void *trait; }; struct traitor_2 { typedef void *trait; }; static_assert(has_trait<traitor_1>::value, "traitor_1::trait is missing"); static_assert(has_trait<traitor_2>::value, "traitor_2::trait is missing"); and the compiler tells me that "has_trait<traitor_1>::value" is "false", while "has_trait<traitor_2>::value" is "true". The above listing skips some key code involving boost::enable_if that appears to break "has_trait". The full listing is below. I would appreciate if any experts could tell me if this behaviour is expected. The complete code listing is below. I've tried: g++-4.6 -std=gnu++0x -I/usr/include/boost-1_49 -c -o/dev/null traitor.cpp g++-4.7 -std=gnu++0x -I/usr/include/boost-1_49 -c -o/dev/null traitor.cpp Thanks very much, Duncan // traitor.cpp # include <boost/utility/enable_if.hpp> # include <boost/mpl/has_xxx.hpp> BOOST_MPL_HAS_XXX_TRAIT_DEF(trait) class C { struct enabler { }; public: template <class T> C(const T &, typename boost::enable_if<has_trait<T>, enabler>::type = enabler()); }; struct traitor_1; void g(const C &); void g(const traitor_1 &); void f(const traitor_1 &t1) { g(t1); } struct traitor_1 { typedef void *trait; }; struct traitor_2 { typedef void *trait; }; static_assert(has_trait<traitor_1>::value, "traitor_1::trait is missing"); static_assert(has_trait<traitor_2>::value, "traitor_2::trait is missing");
I've run into an unexpected behaviour, and I'm not sure if the compiler has a bug, if Boost.MPL has a bug, or if I have the wrong expectations. I am using Boost v1.49.0, but I also tested r82084 of the Boost trunk.
I'm not sure which is the case either, but notice that a simple workaround is to define f after traitor_1 is complete (you can still *declare* f where you do now). This is not an unreasonable requirement, because the body of f performs a traitor_1 -> C conversion, for which it needs to instantiate the constructor of C, for which it needs to instantiate has_traitor<traitor_1>, which only gives accurate results if traitor_1 is complete. Regards, Nate
On 18/12/2012, Nathan Ridge <zeratul976@hotmail.com> wrote:
I'm not sure which is the case either, but notice that a simple workaround is to define f after traitor_1 is complete (you can still *declare* f where you do now).
Yes, you are correct; my actual code is a fair bit more complicated, but there is definitely a workaround. Moreover, to change this to a compile-time error, I'll change the definition of "traitor_1" to include a static assertion as follows. struct traitor_1 { typedef void *trait; static_assert(has_trait<traitor_1>::value, "traitor_1::trait missing"); }; Without a static assertion failing, real code would compile with the wrong value for "has_trait<>". I think adding this to the definition will fix most of the otherwise "silent" bugs. I'm still interested in whether this is a defect, or just a limitation of C++, if anyone has an opinion... Most interesting to me is that using "enable_if<has_trait<T>>" changes the final value of "has_trait<T>" by observing it "too early"...
Duncan Exon Smith wrote:
Nathan Ridge wrote:
I'm not sure which is the case either, but notice that a simple workaround is to define f after traitor_1 is complete (you can still *declare* f where you do now).
Yes, you are correct; my actual code is a fair bit more complicated, but there is definitely a workaround.
Moreover, to change this to a compile-time error, I'll change the definition of "traitor_1" to include a static assertion as follows.
struct traitor_1 { typedef void *trait; static_assert(has_trait<traitor_1>::value, "traitor_1::trait missing"); };
Without a static assertion failing, real code would compile with the wrong value for "has_trait<>". I think adding this to the definition will fix most of the otherwise "silent" bugs.
I'm still interested in whether this is a defect, or just a limitation of C++, if anyone has an opinion...
For what it's worth, I was surprised that has_trait didn't error out when passed an incomplete type. It seems like just having it return false with incomplete types would be problematic for more than just the incorrect deduction -- it could lead to ODR violations, etc. Perhaps the macros should have a sizeof(T) line added. . . Thanks, Nate
Nathan Crookston wrote:
Duncan Exon Smith wrote:
I'm still interested in whether this is a defect, or just a limitation
of C++, if anyone has an opinion...
For what it's worth, I was surprised that has_trait didn't error out when passed an incomplete type. It seems like just having it return false with incomplete types would be problematic for more than just the incorrect deduction -- it could lead to ODR violations, etc.
Perhaps the macros should have a sizeof(T) line added. . .
Looking at the implementation, it appears that the decision to return false in the event of incomplete types is intentional. I found that surprising -- the documentation doesn't mention that, from what I see. Nate
participants (3)
-
Duncan Exon Smith
-
Nathan Crookston
-
Nathan Ridge