Boost logo

Boost :

Subject: Re: [boost] enable_if and non-template member functions?
From: Matt Calabrese (rivorus_at_[hidden])
Date: 2011-08-08 13:44:09


On Mon, Aug 8, 2011 at 8:55 AM, Jeremiah Willcock <jewillco_at_[hidden]>wrote:

> I think the macro would be useful, but looking at the implementation, I'm
> not sure it works with types like you claim. Also, what is the code on
> lines 45-46 doing? I don't see how that would ever be used by the current
> definitions of your macros.
>

It does, try it out. I'll comment the trick a little better or remove it if
people have a problem with it for some reason (it implies a couple of
template instantiations so that's a legitimate criticism, though I don't
think use of the macro would be common enough for it to have a negligible
impact on compile-times). The idea is that I want the single macro to be
usable with values or types (mpl integral constants), so I need to be able
to have the macro expand to a bit of C++ that implicitly converts the bool
constant to an mpl integral constant and leaves the mpl constant as is, or
the other way around. The trick I came up with to accomplish that is this:

//////////
// Make two function template overloads, here both called "foo":

// One that takes a bool constant
// The return type is the mpl integral constant equivalent of the bool
template< bool Value > mpl::bool_< Value > foo();

// And one that takes an mpl integral constant
// The return type is just the argument being passed
template< class Type > Type foo();

// Now if we do:
// decltype( foo< something_goes_here >() )
// We will always get an mpl integral constant representing the argument
// This works if something_goes_here is a bool constant or if it's an mpl
constant

// For example:
// Yields mpl::bool_< true >
typedef decltype( foo< true >() ) condition1;

// Yields boost::is_void< void >
typedef decltype( foo< boost::is_void< void > >() ) condition2;
//////////

Understand? So by using function template overloads and decltype you can
convert the bool to an mpl integral constant without the user having to be
aware. If an mpl integral constant is passed, then it is left alone.

The actual trick in the code is slightly more complex and that is because I
also handle the potentially common case of a bool condition with a greater
than sign in it such as:
sizeof( something ) > sizeof( void* )

This issue here is that the > wouldn't actually be a greater than operator
at all after macro expansion, it would instead end the function template
argument list that appears inside the decltype. A user who doesn't realize
this would get a weird error, which could ultimately be resolved by just
wrapping their expression in parentheses, but that isn't the easiest thing
to notice for most programmers. To make it so that a user doesn't have to
know to do that, I just wrap the argument in parentheses from the inside of
the macro. But now, if I wrap all macro arguments internally in parentheses,
I have the problem that a parenthesized type is not valid on its own! To get
around that, what I do is I append "bool" in front of the parentheses. If
you don't immediately see what that get's you, check it out:

//////////
// In the case of a value, we get a compile-time cast to bool. No problem.
bool( sizeof( something ) > sizeof( void* ) )

// In the case of a type we get a function type that
// returns bool
// takes our condition as a parameter type
bool( is_void< void > )
//////////

Now that I have a bool or a function type with the condition in the first
parameter, I need to rewrite the original overloads:

//////////
// This overload stays the same
// The return type is the mpl integral constant equivalent of the bool
template< bool Value > mpl::bool_< Value > foo();

// This overload is updated to pull out the parameter type instead of the
type directly
// The return type is the parameter type of the function
template< class Type > typename function_traits< Type >::arg1_type foo();
//////////

Just to be complete, here's what the decltype part of the macro looks like
with everything update after macro expansion (it obviously expands to more
than just this, though here is the interesting part -- the function is also
not called "foo" in the actual implementation, it's called
"enable_if_condition_getter"):

//////////
// BOOST_ENABLE_IF( true )
// Internally yields mpl::bool_< true > from the decltype trick
// decltype( foo< bool(true) >() )
//
// BOOST_ENABLE_IF( boost::is_void< void > )
// Internally yields boost::is_void< void > from the decltype trick
// decltype( foo< bool(boost::is_void< void >) >() )
//////////

So now there can exist a single macro that works with mpl integral constants
or bools, even if they have a top-level >, without the user of the macro
having to know anything about the implementation. Anyway, if people really
hate this, it can be removed and replaced by BOOST_ENABLE_IF and
BOOST_ENABLE_IF_C which each have their own separate implementation -- one
for types and one for bools. I just prefer to provide the simplest interface
possible with the least subtleties for the user, even if it complicates the
code a bit behind the scenes. If the template instantiations are perceived
to be a problem, then two macros instead of one is a reasonable option.

-- 
-Matt Calabrese

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