Boost logo

Boost :

Subject: Re: [boost] [Review] TTI
From: Edward Diener (eldiener_at_[hidden])
Date: 2011-07-28 17:30:11


On 7/28/2011 2:07 PM, Jeffrey Lee Hellrung, Jr. wrote:
> 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).

You are returning an instance of T in the signature. Does not this, for
the sake of the compiler, mean that T must be constructible ? If it does
not mean this, then it is a clever metaprogramming trick which I did not
realize could be used to generate an expression at compile time where
one can check that an instance of T can call a member function ( your
declval<derived_t>().xxx(declval<T0>()) below ). In TTI the technique
used has been merely to check if the address of a member function
matches a pointer to the correct type.

>
>
>> 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.

I know what you are doing in the code. What I meant is that you do not
want the exact same signature for xxx in base_t as the member function
of the enclosing class being tested. In that case it is much more
probable that 'void xxx(...)', as opposed to 'void xxx()', does not
duplicate the signature of a member function called 'xxx', the former
being much more rarely used in C++ than the latter.

> 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 :(

You mean if it is a static member function instead ?

>
> 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?

Bingo !

> 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 :/

I believe Frederick Bron had to tackle the latter and I remember
discussions about dealing with it by Eric Niebler among others.

>
> 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).

No it does not surprise me but first things first.

>
> 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 understand in general how to use pp-lib to generate the code for some
maximum number of parameters. As much as I admire pp-lib, if there is a
non-macro using solution to most anything, I would rather use that.

>
>
>> 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.

I will study what Frederick has done and try to use what you have above
and what he has done for TTI. If there is some overlap I will not tread
on his territory unless the functionality I may try to provide is
essentially undoable by what he has already done. It is also possible
that common metafunctions, which are details to both implementations,
between the libraries can be shared.

Eddie


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