Patch: bug fix for PR18218

Marshall Clow mclow.lists at gmail.com
Fri Dec 27 15:21:43 PST 2013


On Dec 19, 2013, at 3:38 PM, Howard Hinnant <howard.hinnant at gmail.com> wrote:

> On Dec 19, 2013, at 6:36 PM, Matt Calabrese <rivorus at gmail.com> wrote:
> 
>> Ah, I'm out of my domain, I didn't realize the wording of [c.math] 11
>> in addition to [global.functions] was what was causing the need for
>> the templates... I think I'm starting to understand the full issues
>> here. One solution might be something along these lines:
>> 
>> 1) Declare the overloads that are explicitly specified. These are just
>> 3 overloads total for each IIUC.
>> 2) Declare a template overload that takes r-value references for each
>> argument that is enabled if and only if the corresponding function
>> would be callable with the forwarded arguments (this can be done with
>> a metafunction implemented via SFINAE). This function would internally
>> cast the arguments to the corresponding type and invoke the actual
>> function (therefore it exhibits the same observable behavior as if
>> this template didn't exist).
>> 3) Declare a second template overload that takes r-value references
>> for each argument but that is DISABLED if the corresponding function
>> would be callable with the forwarded arguments. This overload would be
>> the one that does something along the lines of the trick that was
>> originally implemented (though adjusted for r-value refs). It would do
>> an is_arithmetic check rather than the __numeric_type trick.
>> 
>> The overall result would be that anything that would have been
>> callable with only the original overloads would still work and have
>> the same behavior. The enable_if makes sure that the fallback would be
>> used only in the cases where such a call would have failed with only
>> the explicitly specified overloads. I think this satisfies all of the
>> standard's requirements.
> 
> <nod> I've been thinking the same thing since your first reply.  Thanks for bringing this issue up.

So, I’ve implemented the following scheme, which I think works as well as we can get:

template <typename _Fp>
void printFP ( const char *lead ) {
	if ( std::is_same<_Fp, float>::value )
		std::cout << lead << " is float" << std::endl;
	if ( std::is_same<_Fp, double>::value )
		std::cout << lead << " is double" << std::endl;
	if ( std::is_same<_Fp, long double>::value )
		std::cout << lead << " is long double" << std::endl;
	}


bool three_arg ( float, float, float )    { std::cout << "float" << std::endl; return true; }
bool three_arg ( double, double, double ) { std::cout << "double" << std::endl; return true; }
bool three_arg ( long double, long double, long double ) { std::cout << "long double" << std::endl; return true; }

#if __cplusplus >= 201103L

template <class _A1, class _A2, class _A3>
inline _LIBCPP_INLINE_VISIBILITY
typename std::enable_if<
	!std::is_same<typename numeric_type<_A1>::type, void>::value &&
	!std::is_same<typename numeric_type<_A2>::type, void>::value &&
	!std::is_same<typename numeric_type<_A3>::type, void>::value, 
	bool>::type
three_arg(_A1 &&__x, _A2 &&__y, _A3 &&__z) _NOEXCEPT
{
	typedef typename numeric_type<_A1>::type _D1;
	typedef typename numeric_type<_A2>::type _D2;
	typedef typename numeric_type<_A3>::type _D3;
	printFP<_D1> ( "three (1):      " );
	printFP<_D2> ( "three (2):      " );
	printFP<_D3> ( "three (3):      " );
        typedef typename std::__promote<_D1, _D2, _D3>::type type;
	printFP<type> ( "three (common): " );
	return true;
//     return std::fma (
//     	(type) _D1(__x), (type) _D2(__y), (type) _D3(__z));
}
#else
template <class _A1, class _A2, class _A3>
inline _LIBCPP_INLINE_VISIBILITY
typename std::enable_if<
	!std::is_same<typename numeric_type<_A1>::type, void>::value &&
	!std::is_same<typename numeric_type<_A2>::type, void>::value &&
	!std::is_same<typename numeric_type<_A3>::type, void>::value, 
	bool>::type
three_arg(_A1 __x, _A2 __y, _A3 __z) _NOEXCEPT
{
	typedef typename numeric_type<_A1>::type _D1;
	typedef typename numeric_type<_A2>::type _D2;
	typedef typename numeric_type<_A3>::type _D3;
	printFP<_D1> ( "three (1):      " );
	printFP<_D2> ( "three (2):      " );
	printFP<_D3> ( "three (3):      " );
        typedef typename std::__promote<_D1, _D2, _D3>::type type;
	printFP<type> ( "three (common): " );
	return true;
}
#endif

In C++11, unless all three arguments are the same (float, double, long double), we go to the template, where things get passed by reference, and everything gets converted to the right type.

In C++03, we don’t have rvalue references, so we have to take the params by value.
* We can’t take them by non-const reference, because of literal constants like “1.2”
* We can’t take them by const reference because the conversion from user-defined type to float (say) might not be const.
* If the caller doesn’t want the copy to be made, then they can do the cast themselves:
Instead of:
	fma ( 1.2, <something convertible to double>, 23 );
they can write:
	fma ( 1.2, (double) <something convertible to double>, 23 );

— Marshall





More information about the cfe-commits mailing list