Page MenuHomePhabricator

[libc++] Fix PR20084 - std::is_function<void() const> failed.
ClosedPublic

Authored by EricWF on Feb 11 2015, 3:58 PM.

Details

Summary

This patch introduces some black magic to detect const and volatile qualified function types such as void () const.

The patch works in the following way:

We first rule out any type that satisfies on of the following. These restrictions are important so that the test below works properly.

  • is_class<_Tp>::value
  • is_union<_Tp>::value
  • is_void<_Tp>::value
  • is_reference<_Tp>::value
  • __is_nullptr_t<_Tp>::value

If none of the above is true we perform overload resolution on __source<_Tp>(0) to determine the return type.

  • If _Tp& is well-formed we select _Tp& __source(int). _Tp& is only ill formed for cv void types and cv/ref qualified function types.
  • Otherwise we select __dummy_type __source(...). Since we know _Tp cannot be void then it must be a function type.

let R be the returned from __source<_Tp>(0).
We perform overload resolution on __test<_Tp>(R).

  • If R is __dummy_type we call true_type __test(__dummy_type).
  • if R is _Tp& and _Tp& decays to _Tp* we call true_type __test(_Tp*). Only references to function types decay to a pointer of the same type.
  • In all other cases we call false_type __test(...).

__source<_Tp>(0) will try and form _Tp& in the return type. if _Tp& is not well formed the return type of __source<_Tp>(0) will be dummy type. _Tp& is only ill-formed for cv/ref qualified function types (and void which is dealt with elsewhere).

This fixes PR20084 - http://llvm.org/bugs/show_bug.cgi?id=20084

Diff Detail

Event Timeline

EricWF updated this revision to Diff 19792.Feb 11 2015, 3:58 PM
EricWF retitled this revision from to [libc++] Fix PR20084 - std::is_function<void() const> failed..
EricWF updated this object.
EricWF edited the test plan for this revision. (Show Details)
EricWF added reviewers: mclow.lists, rsmith, K-ballo.
EricWF added a subscriber: Unknown Object (MLST).
rsmith added inline comments.Feb 11 2015, 4:25 PM
include/type_traits
434–439

Makes sense to me ;-) Though I'd use __source(...) as a more "canonical" way of writing a less-preferred overload, as we do in the declaration of __test.

We'll use the __dummy_type path for both cv-/ref-qualified function types and for void, but it looks like we're handling the void case in __libcpp_is_function, so that's OK.

EricWF updated this object.Feb 11 2015, 6:17 PM

Add an explanation of the patch to the description and address richards inline comments.

include/type_traits
434–439

Why is it that you can't form a reference to a cv/ref qualified function type? Could you point me to the standard. Thanks for the review!

rsmith added inline comments.Feb 11 2015, 6:32 PM
include/type_traits
434–439

There are no values of those types; they exist only so that member functions can be declared and pointer-to-member types can be written. The relevant rule is in [dcl.fct](8.3.5)/6 in the current draft.

EricWF updated this revision to Diff 19802.Feb 11 2015, 6:49 PM

Address Richard's comments and remove extra tests that were added to function.pass.cpp (that is tested elsewhere).

mclow.lists edited edge metadata.Feb 17 2015, 9:38 AM

There are 12 different qualifiers that can go on functions.

const
volatile
&
&&

Since & and && are mutually exclusive, that leaves 12 different possibilities.
I only see tests for four here.

I'd love a remove_qualifiers type trait that took a function type and returned the unqualified type. That would be useful in several places; but the only way I've figured out how to do it involves tons of specializations (and even more for the non-variadic compilers).

There are 12 different qualifiers that can go on functions.

const
volatile
&
&&

Since & and && are mutually exclusive, that leaves 12 different possibilities.
I only see tests for four here.

TEST_REGULAR(...) Should test all of the non-reference qualified possibilities.
TEST_REF_QUALIFIED(...) Should test all of the reference qualified ones.

What am I missing?

I'd love a remove_qualifiers type trait that took a function type and returned the unqualified type. That would be useful in several places; but the only way I've figured out how to do it involves tons of specializations (and even more for the non-variadic compilers).

I agree, I'll see what I can do but I don't think that should hold this up.

Look at __member_pointer_traits_imp for a crappy way to do it.

Also, I'm not sure if std::function<void (int, ...) const>, is even legal, but if it is, that's more work.

mclow.lists accepted this revision.Feb 17 2015, 10:17 AM
mclow.lists edited edge metadata.

My bad; I missed that in the tests.

This revision is now accepted and ready to land.Feb 17 2015, 10:17 AM
EricWF closed this revision.Feb 18 2015, 8:33 AM

I just ran into this problem while trying to get range-v3 working on clang-3.6. This is_function implementation probably instantiates fewer templates than the version currently shipping with libc++. Disclaimer: I haven't tested it exhaustively yet.

template<int I>
struct priority_tag
    : priority_tag<I - 1>
{};

template<>
struct priority_tag<0>
{};

// Function types here:
template<typename T>
char (&is_function_impl_(priority_tag<0>))[1];

// Array types here:
template<typename T, typename = decltype((*(T*)0)[0])>
char (&is_function_impl_(priority_tag<1>))[2];

// Anything that can be returned from a function here (including
// void and reference types):
template<typename T, typename = T(*)()>
char (&is_function_impl_(priority_tag<2>))[3];

// Classes and unions (including abstract types) here:
template<typename T, typename = int T::*>
char (&is_function_impl_(priority_tag<3>))[4];

template <typename T>
struct is_function
    : integral_constant<bool, sizeof(is_function_impl_<T>(priority_tag<3>{})) == 1>
{};