C++17 class template argument deduction for variadic template

91 views Asked by At

In order to create a generic factory a need to manage generic creation params was raised.

This is the first step:

struct Foo {
    int num_;
    class FooKey {};
public:    
    Foo(int num, FooKey) : num_(num) {}
    static std::unique_ptr<Foo> create(int i) {
        return std::make_unique<Foo>(i, FooKey());
    }
    int num() const { return num_; }
};

template<class Obj>
class Factory {
public:
    template<typename... ObjParams>
    auto createObj(ObjParams&&... objectCreationParams) {
        // the use of C++17 std::invoke / std::apply is for conveniency
        // it can be done in a more cumbersome way with C++11/C++14
        return std::invoke(&Obj::create, objectCreationParams...);
    }
};

int main() {
    Factory<Foo> f;
    auto fObj = f.createObj(7);
    std::cout << fObj->num() << std::endl;
}

But we want to allow the factory to 'remember' the creation params.

So we add variadic params and go for a creation function for the factory:

template<class Obj, class... ObjParams>
class Factory {

    std::tuple<ObjParams...> _objectCreationParams;

public:
    Factory(ObjParams&& ... params) {
        _objectCreationParams = 
            std::make_tuple<ObjParams...>(std::forward<ObjParams>(params)...);
    }

    auto createObj() {
        return std::apply(&Obj::create, _objectCreationParams);
    }
};

template<class Obj, class... ObjParams>
Factory<Obj, ObjParams...> createFactory(ObjParams&&... objParams) {
    return Factory<Obj, ObjParams...>{std::forward<ObjParams>(objParams)...};
}

int main() {
    auto factory = createFactory<Foo>(7);
    auto fObj = factory.createObj();
    std::cout << fObj->num() << std::endl;
}

We want to replace now the creation function for the factory with C++17 class template argument deduction, but this fails to compile (g++ 7.2.0). I guess I'm missing something with C++17 class template argument deduction rules.

Any suggestion?

template<class Obj, class... ObjParams>
class Factory {

    // ...

public:
    template<class... ObjParams_>
    Factory(ObjParams_&& ... params) {
        _objectCreationParams = 
            std::make_tuple<ObjParams_...>(std::forward<ObjParams_>(params)...);
    }

    // ...
};

// user-defined deduction guide 
template<class Obj, class... ObjParams>
Factory(ObjParams&& ... params) -> Factory<Obj, ObjParams...>;

int main() {
    Factory<Foo> f(7); // compilation error - candidate expects 0 arguments, 1 provided
        // shouldn't it auto deduce <class... ObjParams> for Factory?
    auto fObj = f.createObj();
    std::cout << fObj->num() << std::endl;
}

Edit:

Given that there is no partial class template argument deduction suggesting the following:

template<class Obj>
struct Factory {
    template<class... ObjParams>
    class WithParams {
        std::tuple<ObjParams...> _objectCreationParams;
    public:
        WithParams(ObjParams&& ... params) {
            _objectCreationParams = 
                std::make_tuple<ObjParams...>(std::forward<ObjParams>(params)...);
        }

        auto createObj() {
            return std::apply(&Obj::create, _objectCreationParams);
        }
    };
    template<class... ObjParams>
    auto createObj(ObjParams&& ... params) {
        return std::invoke(&Obj::create, std::forward<ObjParams>(params)...);
    }
};

int main() {
    Factory<Foo>::WithParams f(7);
    auto fObj = f.createObj();
    std::cout << fObj->num() << std::endl;

    Factory<Foo> f2;
    auto fObj2 = f2.createObj(8);
    std::cout << fObj2->num() << std::endl;
}
0

There are 0 answers