Write the type of a pointer to member data from other template types

Viewed 66

I have a function that extracts pointer to member type from other types, and this works:

template<class T2, class Array,
    class Element = typename std::decay_t<Array>::element,
    class PointerToMemberType = T2 Element::*
>
v f(Array&& a, PointerToMemberType pm);

However I cannot find the way to write PointerToMemberType = ???, without first definining Element, which should be able to be omitted.

How can I write PointerToMemberType directly without using the auxiliary Element?

I tried plain substitution but it doesn't work:

template<
    class T2, class Array, 
    class PointerToMemberType = T2 typename std::decay_t<Array>::element::*
>
void f(Array&&, PointerToMemberType pm);
// error: expected identifier before ‘*’ token
  677 |  class PointerToMemberType = T2 typename std::decay_t<Array>::element::* 
                                                                               ^

I also tried adding typename and parenthesis in several places.

Note that PointerToMemberType is not used for deduction at the moment, although I would try to use it in the future.

In some places it recommends using std::invoke so one doesn't need to deal with pointers-to-data-members, How would that fit or simplify something here?

1 Answers

A helper metafunction would do the trick quite nicely:

template <typename C, typename M>
using PM = M C::*;

template<
    class T2, class Array, 
    class PointerToMemberType = PM<typename std::decay_t<Array>::element, T2>
>
...

As for whether it can be done "directly", the answer is yes, but you must omit the typename keyword. Your compiler should accept this:

template<
    class T2, class Array, 
    class PointerToMemberType = T2 std::decay_t<Array>::element::*
>
...

According to the standard, [temp.res]/5:

A qualified name used as the name in a class-or-decltype (Clause 13) or an elaborated-type-specifier is implicitly assumed to name a type, without the use of the typename keyword. In a nested-name-specifier that immediately contains a nested-name-specifier that depends on a template parameter, the identifier or simple-template-id is implicitly assumed to name a type, without the use of the typename keyword. [ Note: The typename keyword is not permitted by the syntax of these constructs. — end note ]

In our situation, we have the nested-name-specifier std::decay_t<Array>::element:: which immediately contains the nested-name-specifier std::decay_t<Array>:: which depends on a template parameter, so this paragraph tells us that typename is not necessary. Clearly, when std::decay_t<Array>::element is followed by ::, the compiler knows that std::decay_t<Array>::element is a type and not a data member nor a member function.

According to the note, the grammar forbids the unnecessary use of typename in this situation. The proper use of a typename-specifier according to [temp.res]/3 is:

typename nested-name-specifier identifier

Here, typename applies to the identifier after the entire nested-name-specifier has been applied, for example, in typename A::B::C::D, the :: operator binds more tightly than typename, so you are saying that typename A::B::C::D is a type. A nested-name-specifier cannot contain typename at the top level of one of its components, since from [expr.prim.id.qual], the leftmost component can only be a type-name, a namespace-name, or a decltype-specifier, and a type-name (unlike a type-id) can only be an unqualified name or a simple-template-id. Non-leftmost components can only be unqualified names or simple-template-ids (sometimes required to be prefixed by template).

Related