2

I have a bunch of structs, each of which has an 'array' member and a size indicator:

struct S {
    size_t num;
    int arr[100];
};

struct V {
    float array[10];
    int size;
};

I want to create manipulator objects for each struct:

template <typename Type, typename ElementType, size_t N, typename SizeType>
struct StructManipulator {
    Type* resource_;
    ElementType Type::*elements[N];
    SizeType Type::*sizeIndicator;

    void set(ElementType val, size_t idx)
    {
        resource_->*elements[idx] = val;
    }
};

template <typename Type, typename ElementType, size_t N, typename SizeType>
StructManipulator<Type, ElementType, N, SizeType> makeStructManipulator(Type* resource,
        ElementType Type::*elements[N], SizeType Type::*sizeIndicator)
{
    return StructManipulator<Type, ElementType, N, SizeType>{resource, elements, sizeIndicator};
}           

StructManipulator will let me manipulate array elements independent of what the offset of the array and the size indicator are within the structs. All StructManipulator is the offset of the 'array' within a struct, the offset and the type of the size indicator and a pointer to the struct object.

However, when I attempt to create a StructManipulator:

int main()
{
    S s;
    auto m = makeStructManipulator(&s, &S::arr, &S::num);
    m.set(5, 4);
}

I get this error:

main.cpp: In function 'int main()':
main.cpp:39:56: error: no matching function for call to 'makeStructManipulator(S*, int (S::*)[100], size_t S::*)'
     auto m = makeStructManipulator(&s, &S::arr, &S::num);
                                                        ^
main.cpp:29:51: note: candidate: template<class Type, class ElementType, long unsigned int N, class SizeType> StructManipulator<Type, ElementType, N, SizeType> makeStructManipulator(Type*, ElementType Type::**, SizeType Type::*)
 StructManipulator<Type, ElementType, N, SizeType> makeStructManipulator(Type* resource,
                                                   ^~~~~~~~~~~~~~~~~~~~~
main.cpp:29:51: note:   template argument deduction/substitution failed:
main.cpp:39:56: note:   mismatched types 'ElementType Type::**' and 'int (S::*)[100]'
     auto m = makeStructManipulator(&s, &S::arr, &S::num);

I looks like I could not declare the type of the "pointer to an array member" correctly? What should have been the correct declaration?

Benji Mizrahi
  • 2,154
  • 2
  • 23
  • 38
  • 3
    (why don't you use a vector?) – user202729 Jan 23 '18 at 06:32
  • what's wrong with `s.arr[4]=5`? or `auto p=s.arr; p[4]=5`? – apple apple Jan 23 '18 at 06:37
  • 1
    These ``struct``s are from a legacy code, it is out of my control. They exist and will continue to exist. I want to write one single code to manipulate all these struct objects, to practice DRY principle. – Benji Mizrahi Jan 23 '18 at 06:52
  • 1
    Two questions. (1) Why combine a pointer to struct with pointers-to-members of that struct? The whole point of pointers-to-members is that they can be used separately from their container objects. Your manipulators could just as well have a pair of `T*` and `int*` instead. (2) Why use disparate arrays and size indicators? If array with a size indicator is a recurring thing in your code, encapsulate them in a class (template) and use that. – n. m. could be an AI Jan 23 '18 at 06:55
  • @n.m. This is a simplified version of my code. This code snippet is only to demonstrate to problem I had about the declaration of "pointer to array member". In my real code, I need to 1) create and keep these struct objects, 2) update any of the values within their member arrays. I specifically used "pointer to array member" instead of T* & int*, this way I automatically acquire the capacity (N) of the array. You asked why not encapsulate in a template class but I believe this is what StructManipulator is for. – Benji Mizrahi Jan 23 '18 at 07:20
  • (1) You can have `T (*)[N]` or `T (&)[N]` just as well, no need for a pointer to member for that. (2) your manipularor encapsulates access to the array, I'm talking about encapsulating the array itself, storage and all (though if you don't control containing structs it is less relevant). – n. m. could be an AI Jan 23 '18 at 07:35
  • @n.m. But these would be absolute memory addresses. If I copy/move the object, the pointers would become invalid. However, if I store the offsets I can safely copy/move my objects. – Benji Mizrahi Jan 23 '18 at 08:11
  • In your current setup, if you move the struct, resource_ becomes invalid. What exactly is the win here? – n. m. could be an AI Jan 23 '18 at 09:09
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/163705/discussion-between-benji-mizrahi-and-n-m). – Benji Mizrahi Jan 23 '18 at 10:32

2 Answers2

3

Pretty much the same syntactic rules that apply for pointer declarators also apply to pointer to member declarators. This means that elements needs to be defined like this:

ElementType (Type::* elements)[N];

Otherwise you get yourself an array of pointers to members, instead of a pointer to member array.

And then you better parenthesize any access through it, as you should with other pointers to members that are used for postfix expressions (such as function calls):

(resource_->*elements)[idx] = val;
StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
2

In C++17, you can use std::invoke to invoke class members (not only member functions), instead of using the .* and ->* operators with their inconvenient precedence rules.

So, for @StoryTeller's

ElementType (Type::* elements)[N];

you can access the array by writing

std::invoke(elements, resource_)[idx] = val;
krzaq
  • 16,240
  • 4
  • 46
  • 61