sigalor
sigalor

Reputation: 1369

Use variadic function as template argument

I'd like to make the following code work without changing the Child1 and Child2 classes at all:

#include <iostream>

int triple(int a) {
    return a * 3;
}

int add(int a, int b) {
    return a + b;
}

template<int (*F)(int)>
class Parent {
    public:
        Parent(int a) {
            std::cout << "constructed: " << F(a) << std::endl;
        }
};

class Child1 : Parent<triple> {
    public:
        Child1(int a) : Parent(a) {}
};

/*class Child2 : Parent<add> {
    public:
        Child2(int a, int b) : Parent(a, b) {}
};*/

int main() {
    Child1 child(4);
    //Child2 child(5, 6);
    return 0;
}

As you can see for example, Child1 inherits from a Parent that has been instantiated with the triple function. Thus, when Child1 is instantiated with a four, it outputs "constructed: 12".

In contrast, Child2 is commented out, as it obviously does not work yet. In the main function, I am trying to pass two arguments to the Child2 constructor, just like the underlying add() function expects it. Yet, Parent's constructor only accepts a single argument and will probably need template<typename Args...> in front of it for the solution. Additionally, the Parent class will need a template argument like int (*F)(Args...). Eventually, constructing a Child2 instance like the main function does shall output "constructed: 11".

How can I accomplish this, i.e. create a template argument which is a function that can have any number of parameters? Again, please note that Parent class's code is the only thing that may be changed.

Upvotes: 0

Views: 70

Answers (2)

max66
max66

Reputation: 66200

It's too late to play?

I propose a variation of the auto based C++17 VTT's answer that use class specialization to extract the Args... input types (and the Ret type, if useful).

I mean

template <auto>
class Parent;

template <typename Ret, typename ... Args, Ret(*F)(Args...)>
class Parent<F>
 {
   public:
      Parent (Args const & ... as)
       { std::cout << "constructed: " << F(as...) << std::endl; }
 };

The following is a full compiling example

#include <iostream>

int triple (int a)
 { return a * 3; }

long add (int a, int b)
 { return a + b; }

template <auto>
class Parent;

template <typename Ret, typename ... Args, Ret(*F)(Args...)>
class Parent<F>
 {
   public:
      Parent (Args const & ... as)
       { std::cout << "constructed: " << F(as...) << std::endl; }
 };

class Child1 : public Parent<triple>
 {
    public:
        Child1 (int a) : Parent{a}
         { }
 };

class Child2 : public Parent<add>
 {
    public:
        Child2 (int a, int b) : Parent{a, b}
         { }
 };

int main()
 {
   Child1 c1{4};
   Child2 c2{5, 6};
 }

Loose perfect forwarding but gain control over the number (and the types) of arguments.

Upvotes: 4

user7860670
user7860670

Reputation: 37549

With C++17 You can use deduced non-type template parameter and make constructor a variadic template:

template<auto x_pointer_to_function>
class Parent
{
    public:
    template<typename... x_Args>
    Parent(x_Args &&... args)
    {
        std::cout << "constructed: " << ((*x_pointer_to_function)(::std::forward<x_Args>(args)...)) << std::endl;
    }
};

online compiler

Upvotes: 5

Related Questions