Boost logo

Boost :

Subject: Re: [boost] [Review] TTI
From: Jeffrey Lee Hellrung, Jr. (jeffrey.hellrung_at_[hidden])
Date: 2011-07-28 14:07:54


On Thu, Jul 28, 2011 at 10:37 AM, Edward Diener <eldiener_at_[hidden]>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_at_[hidden]**
>> >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


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk