Most probably your setOutput member function is reading some possibly multi byte value, changing a bit depending on the pin, and writing it back.
C++ arrays can't have member functions.
To achieve a similar effect, you should split the work your original setOutput is doing:
- Read some hardware config
 
- Do the bit twiddling
 
- Apply the changes
 
Then you can let multiple pins do their bit twiddling before applying the final output.
Example:
Pin::Config cfg = Pin::read_config();
// the following could also happen in a loop over an array.
cfg = pin1.enableOutput(cfg);
cfg = pin4.enableOutput(cfg);
// or pass by reference and mutate, if you
// want a less functional style
// e.g. pinN.enableOutput(cfg)!
Pin::write_config(cfg);
This way you still have good encapsulation, but better control. Then you can write a free function to operate on arrays, vectors or whatever collection of pins, if needed:
template<typename It>
void setOutputPins(It start, It end) {
  Pin::Config cfg = Pin::read_config();
  for (; start != end; ++start) {
    cfg = (*start).enableOutput(cfg);
  }
  Pin::write_config(cfg);
};
Using it with C arrays:
Pin array[5]; // or a pointer for dynamic arrays
// ...
setOutputPins(array, array + size);
Don't make everything OO. It'll make your life harder.
[...] a function that returns the offset value for each Pin [...]. Then I bitwise or them all together.
So you don't even need that reading step. And since you bitwise or them, you could even do something like this:
Pin::write_config(pin1.output_mask() | pin4.output_mask());
You can make the function generic, too: Or pass a member function pointer:
template<typename It>
void setPins(It start, It end, Pin::Config (Pin::*fn)(void)) {
  Pin::Config cfg = 0; // identity for bitwise or
  for (; start != end; ++start) {
    cfg |= ((*start).*fn)();
  }
  Pin::write_config(cfg);
};
And the pass a pointer to the member function you want to invoke:
setPins(array, array + size, &Pin::asInput);
Example here.