
2011/7/15 Frédéric Bron <frederic.bron@m4x.org>
I am still working on the implementation of the traits to detect operators. I have an issue with operators returning a volatile object (being a reference or not). This breaks the code I wrote to detect if the return type is void or not. In particular, the following code compiles with RET=int but not with RET=ret. Can somebody tell me why?
It seems that any volatile object (reference or not) cannot be bound to a const reference apart for fundamental types. So is there any useful case to return a volatile object? If not, I can maybe just ignore those cases or it becomes to be a nightmare. By the way, it is 1:30am. I am going to bed!
I'm surprised by this; I've attached what I believe to be a more complete test that compiles on MSVC9, and all the commented-out lines fail to compile. Indeed, we can make a little table to summarize what's allowed by MSVC9 and what isn't (best when viewed in a fixed-width font). Here, int_c, int_v, and int_cv are int const, int volatile, and int const volatile, respectively, and similarly for X. The table shows which expressions g< To
::_(f< From >()) are allowed, where
template< class T > T f(); template< class T > struct g { static int _(T); static void _(...); }; I use the above funky definition of g to (try to) prevent MSVC from binding rvalues to lvalues(-to-non-const). To\From | int int_c int_v int_cv int& int_c& int_v& int_cv& int | + + + + + + + + int_c | + + + + + + + + int_v | + + + + + + + + int_cv | + + + + + + + + int& | - - - - + - - - int_c& | + + - - + + - - int_v& | - - - - + - + - int_cv& | - - - - + + + + To\Fr | X X_c X_v X_cv X& X_c& X_v& X_cv& X | + + - - + + - - X_c | + + - - + + - - X_v | + + - - + + - - X_cv | + + - - + + - - X& | - - - - + - - - X_c& | + + - - + + - - X_v& | + - + - + - + - X_cv& | + + + + + + + + The important things I see are: (a) Any qualification (cv or &) of an int can be passed to a function accepting an int by-value, but only at most combinations of const and reference qualifications of X can be passed to a function accepting an X by-value. I.e., volatile qualification anywhere seems to preclude pass-by-value for class types. In both cases, the cv-qualification of the by-value function parameter makes no difference. (b) It looks like pass-by-reference(-to-non-cv) and pass-by-reference-to-const is consistent across fundamental and class types. (c) An int volatile & function parameter will only bind to an int& or int volatile & expression (which makes sense), but an X volatile & function parameter will *additionally* bind to X rvalue and X volatile rvalue expressions! This seems wacky... (d) An int const volatile & function parameter will only bind to an lvalue expression (hmmm...), but an X const volatile & function parameter will bind to any expression (of type X after removing cv and reference qualifications). I don't know if the above table helps you or not, and I don't know what its results are for, e.g., GCC, but I agree that volatile qualification on return types *and* by-reference parameter types seems to give surprising results. What does your code look like that detects void types? I use the following EXPR_IS_VOID macro myself (which is not originally mine, but I think I have modified it from the original after some testing); feel free to pilfer. The double comma operator thingy is intended to work even if the type of ( Expr ) has an overloaded comma operator, but I've found that GCC has issues with this construct, hence the two versions. ---------------- #ifdef __GNUC__ #define EXPR_IS_VOID( Expr ) \ EXPR_IS_CONVERTIBLE( \ ( ( Expr ), ::detail_expr_is_void::void_detector_t() ), \ ::detail_expr_is_void::void_detector_t \ ) #else // #ifdef __GNUC__ #define EXPR_IS_VOID( Expr ) \ EXPR_IS_CONVERTIBLE( \ ( ::detail_expr_is_void::void_detector_t(), \ ( Expr ), \ ::detail_expr_is_void::void_detector_t() ), \ ::detail_expr_is_void::void_detector_t \ ) #endif // #ifdef __GNUC__ namespace detail_expr_is_void { struct any_from_t { any_from_t(...); }; struct non_void_t; struct void_detector_t { #ifdef __GNUC__ template< class T > friend non_void_t operator,(const T&, void_detector_t); template< class T > friend non_void_t operator,(const volatile T&, void_detector_t); #else // #ifdef __GNUC__ non_void_t operator,(any_from_t) const; #endif // #ifdef __GNUC__ }; struct non_void_t { non_void_t operator,(void_detector_t) const; }; } // namespace detail_expr_is_void ---------------- where the EXPR_IS_CONVERTIBLE macro may be defined as (this is simplified from how it's actually defined but is sufficient for use in EXPR_IS_VOID above) ---------------- #define EXPR_IS_CONVERTIBLE( FromExpr, ToType ) \ ( sizeof( ::detail_expr_is_convertible::is_convertible_helper< ToType
::apply( FromExpr ) ) \ == sizeof( ::detail_expr_is_convertible::yes_type ) )
namespace detail_expr_is_convertible { template< std::size_t N > struct sizeof_t { char _dummy[N]; }; typedef sizeof_t<1> yes_type; typedef sizeof_t<2> no_type; template< class T > struct is_convertible_helper { static yes_type apply(T); static no_type apply(...); }; } // namespace detail_expr_is_convertible ---------------- It looks like I get correct results on MSVC9 regardless of the cv and reference qualification, and regardless of the "class'ness" or "fundamental'ness", on the expression type passed to EXPR_IS_VOID. HTH, - Jeff