1

I have a templated class but only part of the template arguments can be deduced from the constructor.

Is there a way to provide the rest of the template arguments inside angle brackets when calling the constructor?

Assume we're using C++17.

template<typename T1, typename T2>
struct S
{
    T2 t2;

    S(const T2& _t2) : t2{_t2} {}

    void operator()(const T1& t1)
    {
        std::cout << t1 << ", " << t2 << '\n';
    }
};

int main()
{
    S<int, double> s {3.14};

    std::function<void(int)> func = s;
    
    func(42);

    // What I want:
    //S<int> s2 {3.14};     <- T1 is provided in the angle brackets, T2 is deduced
    //std::function<void(int)> func2 = s;
    //func2(42);
}

As far as I know we need to either provide all the template arguments in angle brackets or none of them and use CTAD. The problem is that I don't want to write all the template arguments (in my actual use case there's like 5-6 of them and they are quite verbose) but I also don't want to pass all the arguments in the constructor because some of them are not used to construct the object. I just need their types for the operator() method.

I cannot make the operator() method templated because I want to bind it to a std::function object and I cannot deduce the template parameter types during the bind. So that's why I need all the types in the wrapping class.

This partial template deduction exists for functions.

For example:

template<typename T1, typename T2>
void foo(const T2& t2)
{
    T1 t1{};
    std::cout << t1 << ", " << t2 << '\n';
}

int main()
{
    foo<int>(3.4); //T1 is explicitly int, T2 is deduced to be double
}

My current solution is to exploit this feature and construct the object through a function:

template<typename U1, typename U2>
S<U1, U2> construct_S(const U2& t2)
{
    return S<U1, U2>{t2};
}

int main()
{
    auto s2 = construct_S<int>(1.5);
    std::function<void(int)> func2 = s2;
    func2(23);
}

I find this solution clumsy because we're using an external function to construct the object.

I was wondering if there's a cleaner solution for doing this.

Maybe something with deduction guides? I'm not sure.

Zamfir Yonchev
  • 151
  • 1
  • 10
  • 2
    https://stackoverflow.com/q/57563594/817643 – StoryTeller - Unslander Monica Dec 07 '21 at 12:19
  • And https://stackoverflow.com/q/41833630/817643 – StoryTeller - Unslander Monica Dec 07 '21 at 12:22
  • I checked this thread but I don't think the solution there is applicable in my case. – Zamfir Yonchev Dec 07 '21 at 12:23
  • 2
    You can also split the template on two, nest one in the other and use CTAD for the inner one: `auto s = S::impl(42);` – n. m. could be an AI Dec 07 '21 at 12:31
  • 1
    `make_XXX` was the way before CTAD and allow "partial" deduction. – Jarod42 Dec 07 '21 at 12:39
  • 1
    this answer does apply https://stackoverflow.com/a/57563652/4117728. And this answer https://stackoverflow.com/a/57563907/4117728 shows how to use a `make_...` similar to your approach. Its not the answer you wanted to hear, but that doesnt make it less of a duplciate – 463035818_is_not_an_ai Dec 07 '21 at 12:40
  • Else you still have the possibility to add a tag to deduce type: `template struct Tag{}; ` and `template struct S { S(Tag, const T2&); /*..*/};` – Jarod42 Dec 07 '21 at 12:41
  • fwiw, what you call "clumsy" is one of the cleanest solutions. There are other ways (eg what n.1.8 suggested) but any will require a bit of boilerplate to be written – 463035818_is_not_an_ai Dec 07 '21 at 12:43
  • 463035818_is_not_a_number I missed the second part of https://stackoverflow.com/questions/57563594/partial-class-template-argument-deduction-in-c17/57563907#57563907 The deduction guide part is not applicable for my case. The make_ part is indeed what my current solution is. So it seems there's no way around it. – Zamfir Yonchev Dec 07 '21 at 12:44
  • I don't mind a bit of boilerplate as long as it simplifies the user code. I'll think about how to implement n.1.8's solution. – Zamfir Yonchev Dec 07 '21 at 12:50
  • "I cannot make the operator() method templated because I want to bind it to a std::function object, so that's why I need all the types in the wrapping class." Huh? You can bind templated `operator()` fine. – Yakk - Adam Nevraumont Dec 07 '21 at 13:36
  • @Yakk-AdamNevraumont What I meant is that if I declare my operator() as a template I cannot deduce its template parameters because in my actual case it takes no arguments. I updated the question body to avoid confusion. – Zamfir Yonchev Dec 07 '21 at 15:19
  • But, it just works if you remove `typename T1` and add it to the `operator()`. Answer below. – Yakk - Adam Nevraumont Dec 07 '21 at 15:52
  • Actually, my final solution was a class which wraps the `T2` object and a `make` method, which accepts the `T1` template argument and returns a lambda which combines the `T1` and `T2` objects and stamps out the final implementation. Here's the code: https://onlinegdb.com/K6Cc0Y1va – Zamfir Yonchev Dec 12 '21 at 11:57

3 Answers3

3

As mentioned in a comment, you can use a nested class such that the two parameters can be provided seperately (one explicitly the other deduced):

template<typename T1>
struct S {
    template <typename T2>
    struct impl {
        T2 t2;    
        impl(const T2& _t2) : t2{_t2} {}
    };

    template <typename T2>
    impl(const T2&) -> impl<T2>;
};


int main() {
    S<int>::impl<double> s {3.14};
    S<int>::impl s2 {3.14};    // <- T1 is provided in the angle brackets, T2 is deduced
}

I found this How to provide deduction guide for nested template class?. Though, the above code compiles without issues with both gcc and clang: https://godbolt.org/z/MMaPYGbe1.

If refactoring the class template is not an option, the helper function is a common and clean solution. The standard library has many make_xxx functions, some of them were only needed before CTAD was a thing.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
0

The simplest way is to provide factory function which will take over deduction of types:

template<typename T1, typename T2>
auto makeS(T2 x) -> S<T1, T2>
{
    return S<T1, T2>{x};
}

https://godbolt.org/z/4cPTdv7e3

Marek R
  • 32,568
  • 6
  • 55
  • 140
0
template<typename T2>
struct S
{
    T2 t2;

    S(const T2& _t2) : t2{_t2} {}
    template<typename T1>
    void operator()(const T1& t1)
    {
        std::cout << t1 << ", " << t2 << '\n';
    }
};

int main()
{
    S s {3.14}; // T1 not provided
    std::function<void(int)> func2 = s; // T1 deduced by std function
    func2(42); // works
}

I just removed T1 and made it a template argument of operator() and everything works.

Live example.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • It's not applicable in my case. Sorry, it is not shown in my code example but imagine if the operator() definition was like this: `void operator()() { T1 t1{}; std::cout << t1 << ", " << t2 << '\n'; }` You cannot deduce the type of T1 because it's not part of the arguments to the method. – Zamfir Yonchev Dec 07 '21 at 16:06