
On Thu, Jul 28, 2011 at 10:37 AM, Edward Diener <eldiener@tropicsoft.com>wrote:
On 7/27/2011 11:49 PM, Jeffrey Lee Hellrung, Jr. wrote:
On Wed, Jul 27, 2011 at 4:23 PM, Edward Diener<eldiener@tropicsoft.com**
wrote: [...]
I am interested in what you may be trying to do here, so that is why I am
pointing this out.
Can you correct and explain what you are doing again ? I would like to understand the technique for checking convertibility.
[...]
The following program should illustrate the basic idea. Obviously, this is pretty minimalistic, so it doesn't take into account constness, only checks unary member functions, doesn't account for void result types, etc.
Tested on MSVC9.
--------
#include<boost/mpl/and.hpp> #include<boost/static_assert.**hpp>
template< class T> T declval();
I think this means that T must have a default constructor ?
Hmmm...no, but it does require T to be copyable (or movable, I guess)...however, if you want to be safe, just make T a reference type (making declval<T>() an lvalue).
If so, it is limiting for the enclosing type and the parameter types of the function signature.
The idea is to take any reference qualifiers (or lack thereof) on the enclosing type and parameter types literally, to preserve rvalue/lvalue-ness. That's straightforward to do for the parameter types (nothing special need be done), but I did *not* take such care for the enclosing type (notice the lack of boost::remove_reference's below), just for simplicity of exposition.
struct yes_type { char _dummy[1]; };
struct no_type { char _dummy[2]; };
template< class T> struct is_convertible { static yes_type apply(T); static no_type apply(...); };
struct dummy_result_t { }; yes_type is_dummy_result_t(dummy_**result_t); no_type is_dummy_result_t(...);
struct base_t { void xxx( ) { } };
Can this be
struct base_t { void xxx(...) { } };
to avoid conflict with a user's nullary function, as a variable parameter list is far less likely than a nullary function ?
Well it really doesn't matter, you just need base_t to have *some* member function called xxx, and I think one could argue that "void xxx ( )" is the simplest such declaration :) You actually *want* it to conflict with T::xxx. To elaborate a little more, base_t is only used to determine if T has a member function called xxx (signature compatibility is dealt with separately). This is effected by defining the derived_t struct struct derived_t : T, base_t { }; and trying to take the address of derived_t::xxx via the call to has_mem_fn_test. If T::xxx exists, this will be ambiguous and SFINAE will kick in to select that has_mem_fn_test< derived_t >(...) overload, returning yes_type. Otherwise, if T::xxx does not exist, derived_t::xxx refers to base_t::xxx, and &base_t::xxx has type void (base_t::*)( ), so the has_mem_fn_test< derived_t >(int) overload is valid and preferred, returning no_type. Come to think of it, I'm not sure what happens when T::xxx exists but is not a member function...hmmm...I don't think I ever tested that case :( template< void (base_t::*)( )>
struct has_mem_fn_detector { typedef no_type type; };
template< class T> typename has_mem_fn_detector< &T::xxx>::type has_mem_fn_test(int);
template< class T> yes_type has_mem_fn_test(...);
template< class T> struct derived_t : T { using T::xxx; dummy_result_t xxx(...) const; };
template< class T, class Signature> struct has_mem_fn_helper;
template< class T, class Signature = void> struct has_mem_fn : boost::mpl::and_< has_mem_fn< T, void>, has_mem_fn_helper< T, Signature> > { };
template< class T> struct has_mem_fn< T, void> { struct derived_t : T, base_t { }; static const bool value = sizeof( has_mem_fn_test< derived_t>(0) ) == sizeof( yes_type ); typedef has_mem_fn type; };
template< class T, class Result, class T0> struct has_mem_fn_helper< T, Result ( T0 )> { static const bool value = (sizeof( is_dummy_result_t(declval< derived_t<T>
().xxx(declval<T0>())) ) == sizeof( no_type ))
&& (sizeof( is_convertible< Result>::apply(declval< derived_t<T>
().xxx(declval<T0>())) ) == sizeof( yes_type ));
typedef has_mem_fn_helper type; };
struct X { };
struct Y { int xxx(int); };
int main(int argc, char* argv[]) { BOOST_STATIC_ASSERT(!(has_mem_**fn< X>::value)); BOOST_STATIC_ASSERT( (has_mem_fn< Y>::value)); BOOST_STATIC_ASSERT( (has_mem_fn< Y, int ( int )>::value)); BOOST_STATIC_ASSERT( (has_mem_fn< Y, long ( short )>::value)); BOOST_STATIC_ASSERT(!(has_mem_**fn< Y, int ( void* )>::value)); BOOST_STATIC_ASSERT(!(has_mem_**fn< Y, void* ( int )>::value)); return 0; }
Very neat. Of course T0 would have to be expanded to more possible types for function parameter matching.
You mean other arities than 1? Yeah, that's among the various things I left out. Also const correctness and lvalue/rvalue preservation need to be added. And all that is much easier than addressing void result types, which requires another round of indirection :/ This probably wouldn't surprise you given my comments on TTI, but I've also found it useful to include a 3rd template parameter, which is an MPL lambda expression to be applied to the result type of the xxx member function (to enable queries other than convertibility). Maybe a generic method for any number of types would be possible through
using Boost function types.
The way to go, I think, is BOOST_PP_ITERATE (since you have to construct the actual call expression declval<T>().xxx(declval<T0>(), declval<T1>(), ..., declval<T[N-1]>()))...which implies that you'll have to have a #define BOOST_TTI_PARAM_1 foo #define BOOST_TTI_PARAM_2 bar #include BOOST_TTI_HAS_MEMBER_FUNCTION_THINGY() interface rather than a metafunction-generating macro interface. I guess you can use BOOST_PP_REPEAT but it could make the implementation unwieldy and make it potentially very difficult to track down usage errors :/ Variadic templates *might* work here, but I'm really not sure.
I will definitely add this to TTI in some form or other. Thanks for the code !
You may want to coordinate with Frederic Bron and his Type Traits Extension, as this functionality is kind of an overlap between his and your libraries. - Jeff