Search code examples
c++parameter-passingvariadic-functionsrvalue-reference

Pass parameter pack to function with template deduction from return type


I'm writing an object allocator that I'd like to call in the following way:

T result = factoryObject.construct(argA, argB, argC);

I currently have this design, which works...

class Factory {
  void* memPool_;
  
  template <typename Tret, typename... Args>
  Tret construct(Args&&... args) {
    Tret::methodA(std::forward<Args&&>(args));
    Tret::Tret(memPool_, std::forward<Args&&>(args);
  }
}

... as long as I call it with:

T result = factoryObject.construct<T>(argA, argB, argC);

I want to be able to do it without explicitly specifying T. T can get quite complex, and I need to use this factory inline in an initialiser list. I also need to use this in a constructor initialiser list, so I cannot call auto result = factory.construct<T>() (I simply need it to infer the return type based on what it is constructing).

I have tried inferring the type using the operator() trick (as per https://stackoverflow.com/a/2613010/4649183):

public:
    template <class T>
    operator T() { return T(); }
};

class GC {
public:
    static Allocator Allocate() { return Allocator(); }
};

int main() {
    int p = GC::Allocate();
}

... but this does not allow me to pass arguments (since operator() cannot take arguments). I tried storing Args&& args as a tuple as a member of Allocator, repacking the tuple into a parameter pack and calling operator() separately using the logic proposed in the question of Tuple to parameter pack, but

  1. That couldn't actually store references to parameters as far as I could tell (which makes sense because the compiler doesn't know that I'm not going to let the original args go out of scope)
  2. The final solution was so complex that I just must've been going down the wrong path.

What can I do?


Solution

  • You should have all pieces. Just have to assemble them correctly. Code should be similar to:

    template <typename...Ts>
    struct Constructor
    {
        Constructor(void* memPool, Ts... args) :
            memPool{memPool},
            args{std::forward<Ts>(args)...}
        {}
    
        template <typename T>
        operator T() && {
            return std::apply(
                [this](auto&&... args) {
                    T::methodA(std::forward<Args&&>(args));
                    return T::T(memPool_, std::forward<Args&&>(args);
                },
                args);
        }
    
        void* memPool_;
        std::tuple<Ts...> args;
    };
    
    class Factory {
      void* memPool_;
      
      template <typename... Args>
      Constructor<Args&&...>  construct(Args&&... args) {
          return {memPool_, std::forward<Args>(args)...};
      }
    };