
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! Frédéric struct ret { }; #define RET int RET result; RET r() { return result; } RET const rc() { return result; } RET volatile rv() { return result; } // fails to compile RET const volatile rcv() { return result; } // fails to compile RET & rr() { return result; } RET const & rcr() { return result; } RET volatile & rvr() { return result; } // fails to compile RET const volatile & rcvr() { return result; } // fails to compile int main() { RET x; x=r(); x=rc(); x=rv(); // fails with RET=ret x=rcv(); // fails with RET=ret x=rr(); x=rcr(); x=rvr(); x=rcvr(); return 0; }

On 16/07/11 00:22, Frédéric Bron wrote:
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!
Frédéric
struct ret { };
#define RET int
RET result;
RET r() { return result; } RET const rc() { return result; } RET volatile rv() { return result; } // fails to compile RET const volatile rcv() { return result; } // fails to compile RET & rr() { return result; } RET const & rcr() { return result; } RET volatile & rvr() { return result; } // fails to compile RET const volatile & rcvr() { return result; } // fails to compile
int main() { RET x; x=r(); x=rc(); x=rv(); // fails with RET=ret x=rcv(); // fails with RET=ret x=rr(); x=rcr(); x=rvr(); x=rcvr(); return 0; }
I think you're mis-diagnosing this error. Experiments suggest that when RET=int the cv-qualifiers are ignored on the function return type, so rc, rv, and rcv are all defining functions identical to r. When RET=ret the qualifiers are not ignored. I can't find anything in the C++0x standard (N3290) to suggest that this should happen. The only thing I see about cv-qualifiers on return types is footnote 98 in [dcl.fct] p3: 98) As indicated by syntax, cv-qualifiers are a signficant component in function return types. John Bytheway

I think you're mis-diagnosing this error. Experiments suggest that when RET=int the cv-qualifiers are ignored on the function return type, so rc, rv, and rcv are all defining functions identical to r. When RET=ret the qualifiers are not ignored.
I can't find anything in the C++0x standard (N3290) to suggest that this should happen.
Just found the explanation in C++98 standard (3.10 Lvalues and rvalues): "Class rvalues can have cv-qualified types; non-class rvalues always have cv-unqualified types." This confirms what you wrote: cv qualifiers are ignored in non-class return types (int...). Of course, this does not help me a lot... Frédéric

2011/7/16 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!
Frédéric
struct ret { };
#define RET int
RET result;
RET r() { return result; } RET const rc() { return result; } RET volatile rv() { return result; } // fails to compile RET const volatile rcv() { return result; } // fails to compile RET & rr() { return result; } RET const & rcr() { return result; } RET volatile & rvr() { return result; } // fails to compile RET const volatile & rcvr() { return result; } // fails to compile
int main() { RET x; x=r(); x=rc(); x=rv(); // fails with RET=ret x=rcv(); // fails with RET=ret x=rr(); x=rcr(); x=rvr(); x=rcvr(); return 0; }
Hello, I will present my guess, why your example doesn't compile with RET=ret.. but first, a question: Does it ever make sense to ever return an object by value, volatile-qualified? That said, commenting out the two cases that return volatile-qualified ret by value, this example still doesn't compile, because the compiler-generated copy-assign seems to be: ret& operator=( ret const& ) {} and can't be used to copy a volatile-qualified object, so an assignment needs to be added: ret& operator=( ret const volatile& ) {} Now this prevent's the compiler from generating it's own copy-assign, so we also need to add: ret& operator=( ret const& ) {} and now all compiles. I have a feeling, that adding analogous copy constructors would enable returning volatile-qualified objects by value, but I didn't try doing that, since I don't see sense in it. gcc version 4.4.5 (Gentoo 4.4.5 p1.2, pie-0.4.5) Regards Kris

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

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.
#define EXPR_IS_VOID( Expr ) \ EXPR_IS_CONVERTIBLE( \ ( ( Expr ), ::detail_expr_is_void::void_detector_t() ), \ ::detail_expr_is_void::void_detector_t \ )
Interesting: I do not have a series of 2 operator, but instead have non_void_t operator,(const T&, void_detector_t) declared. I will check if your implementation gives something better. Thanks a lot for your contribution. Frédéric

2011/7/17 Frédéric Bron <frederic.bron@m4x.org>
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.
#define EXPR_IS_VOID( Expr ) \ EXPR_IS_CONVERTIBLE( \ ( ( Expr ), ::detail_expr_is_void::void_detector_t() ), \ ::detail_expr_is_void::void_detector_t \ )
Interesting: I do not have a series of 2 operator, but instead have non_void_t operator,(const T&, void_detector_t) declared. I will check if your implementation gives something better. Thanks a lot for your contribution.
I forgot to mention that I only verified that expression types with volatile qualification didn't affect EXPR_IS_VOID on MSVC9; I didn't check GCC, so...given the 2 different implementations, you might still have issues :( More recent versions of GCC may accept the double comma trick; I don't know if it's using non-standard behavior or not :/ - Jeff

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.
Thank you for help. I have tested your code for void detection which GNUC version gives identical results to the one I use (on msvc, g++ and intel): namespace detail {\ struct returns_void_t {}; static ::boost::type_traits::yes_type returns_void(returns_void_t);\ static ::boost::type_traits::no_type returns_void(int);\ } template <typename T> int operator,(const T&, ::detail::returns_void_t); template <typename T> int operator,(const volatile T&, ::detail::returns_void_t); #define RETURNS_VOID_IMPL(Expr)\ sizeof(::boost::type_traits::yes_type)\ ==\ sizeof(::detail::returns_void(((Expr), ::detail::returns_void_t()))) Here are the results (best viewed in fixed size font) of void detection with functions returning various types (int of class X) with various cv qualifiers and by-value or reference: | g++ | g++ |icpc |msvc | return type |4.3.2|4.5.2|10.0 | 10 | |4.3.4|4.5.3|11.1 | | |4.4.4|4.6.0|12.0 | | void | 1 | 1 | 1 | 1 | int | 0 | 0 | 0 | 0 | int const | 0 | 0 | 0 | 0 | int volatile | 1 | 0 | 0 | 0 | int const volatile | 1 | 0 | 0 | 0 | int & | 0 | 0 | 0 | 0 | int const & | 0 | 0 | 0 | 0 | int volatile & | 0 | 0 | 0 | 0 | int const volatile & | 0 | 0 | 0 | 0 | X | 0 | 0 | 0 | 0 | X const | 0 | 0 | 0 | 0 | X volatile | 1 | 1 |fail | 0 | X const volatile | 1 | 1 |fail | 0 | X & | 0 | 0 | 0 | 0 | X const & | 0 | 0 | 0 | 0 | X volatile & | 0 | 0 | 0 | 0 | X const volatile & | 0 | 0 | 0 | 0 | So only msvc gives the right answer, i.e. detects void returning only when void returning. g++ from version 4.5 says that any cv qualifiers for by-value int is ignored which improves the answer for those return types but returning a class by value with volatile qualifiers fails to complile with intel compiler and gives the wrong answer with g++. I wonder why the code gives the right answer withmsvc. I found another issue with g++ (all versions up to 4.6.0) (works fine with msvc 10 and intel 12): although g++ behaves well with fundamental types returned by value, the result of postfix operator++ applied to an int volatile is detected as void! The standard states in 13.6/3 (Built-in operators): "For every pair (T, VQ), where T is an arithmetic type, and VQ is either volatile or empty, there exist candidate operator functions of the form VQ T& operator++(VQ T&); T operator++(VQ T&, int);" That means that for an "int volatile" variable the corresponding built-in operator should be: int operator++(int volatile &, int); and according to the table above this should be correctly detected as returning non-void. Note that prefix operator++ works fine. This is the program that fails (writes true) with g++ (4.5.2 and 4.6.0): #include <iostream> #include <iomanip> #include <boost/type_traits/detail/yes_no_type.hpp> namespace detail {\ struct returns_void_t {}; static ::boost::type_traits::yes_type returns_void(returns_void_t);\ static ::boost::type_traits::no_type returns_void(int);\ } template <typename T> int operator,(const T&, ::detail::returns_void_t); template <typename T> int operator,(const volatile T&, ::detail::returns_void_t); #define RETURNS_VOID(Expr)\ sizeof(::boost::type_traits::yes_type)\ ==\ sizeof(::detail::returns_void(((Expr), ::detail::returns_void_t()))) int main() { int volatile one_int_volatile; std::cout<<std::boolalpha<<"RETURNS_VOID: "<<(RETURNS_VOID((one_int_volatile++)))<<'\n'; return 0; } Frédéric

2011/7/22 Frédéric Bron <frederic.bron@m4x.org>
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.
Thank you for help. I have tested your code for void detection which GNUC version gives identical results to the one I use (on msvc, g++ and intel):
namespace detail {\ struct returns_void_t {}; static ::boost::type_traits::yes_type returns_void(returns_void_t);\ static ::boost::type_traits::no_type returns_void(int);\ } template <typename T> int operator,(const T&, ::detail::returns_void_t); template <typename T> int operator,(const volatile T&, ::detail::returns_void_t);
#define RETURNS_VOID_IMPL(Expr)\ sizeof(::boost::type_traits::yes_type)\ ==\ sizeof(::detail::returns_void(((Expr), ::detail::returns_void_t())))
[...] I wonder, does the double comma trick I had for MSVC also work for GCC > 4.4.1 and/or Intel? And, if so, does it change the results? - Jeff

2011/7/22 Frédéric Bron <frederic.bron@m4x.org> [...]
I found another issue with g++ (all versions up to 4.6.0) (works fine with msvc 10 and intel 12): although g++ behaves well with fundamental types returned by value, the result of postfix operator++ applied to an int volatile is detected as void! The standard states in 13.6/3 (Built-in operators): "For every pair (T, VQ), where T is an arithmetic type, and VQ is either volatile or empty, there exist candidate operator functions of the form VQ T& operator++(VQ T&); T operator++(VQ T&, int);"
That means that for an "int volatile" variable the corresponding built-in operator should be: int operator++(int volatile &, int); and according to the table above this should be correctly detected as returning non-void. Note that prefix operator++ works fine. This is the program that fails (writes true) with g++ (4.5.2 and 4.6.0):
#include <iostream> #include <iomanip> #include <boost/type_traits/detail/yes_no_type.hpp>
namespace detail {\ struct returns_void_t {}; static ::boost::type_traits::yes_type returns_void(returns_void_t);\ static ::boost::type_traits::no_type returns_void(int);\ } template <typename T> int operator,(const T&, ::detail::returns_void_t); template <typename T> int operator,(const volatile T&, ::detail::returns_void_t);
#define RETURNS_VOID(Expr)\ sizeof(::boost::type_traits::yes_type)\ ==\ sizeof(::detail::returns_void(((Expr), ::detail::returns_void_t())))
int main() { int volatile one_int_volatile; std::cout<<std::boolalpha<<"RETURNS_VOID: "<<(RETURNS_VOID((one_int_volatile++)))<<'\n'; return 0; }
Indeed, this is weird. I would hope that operator--(int volatile&, int) at least returns a consistent result :) At the end of the day, it's sounding like you should just paste a huge warning somewhere in the library concerning volatile-qualified by-value return types. - Jeff

Indeed, this is weird. I would hope that operator--(int volatile&, int) at least returns a consistent result :)
Not looked at yet in detail. I have filed a bug to g++ (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=49812) which has been accepted.
At the end of the day, it's sounding like you should just paste a huge warning somewhere in the library concerning volatile-qualified by-value return types.
That's what I am prepared to do because it becomes too tricky. Frédéric

I have filed a bug to g++ (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=49812) which has been accepted.
It is now corrected in the trunk version of g++ (4.7). Frédéric
participants (4)
-
Frédéric Bron
-
Jeffrey Lee Hellrung, Jr.
-
John Bytheway
-
Krzysztof Czainski