2

The best way for me to describe what I am asking is a simple example.

template<typename T>
void execute_example(T* begin, T* end) 
{
    T val = 10 * 0.8;
    while (begin != end) 
    {
        *begin = val;
        val *= 0.8;
        ++begin;
    }
}

using var_t = std::variant<float*, int*, double*>;

// I am just going to assume the type in input is *float to save lines
template<class UnaryOperator>
void my_function(UnaryOperator unary_op, var_t input, size_t size) 
{
    if (input.index() != 0)
        return;
    float* begin = std::get<0>(input);
    float* end = begin + size;
    unary_op<float>(begin, end);
}

int main() 
{
    float* vals = new float[](10);
    my_function(execute_example, vals, 10); // How can I pass execute_example here ??
    delete[] vals;
}

What I am basically trying to figure out how to do is pass a function that needs a template as a function argument. For example, this would work, if instead of a template argument I just set T to float:

void execute_example(float* begin, float* end) 
{
    float val = 10 * 0.8;
    while (begin != end) {
        *begin = val;
        val *= 0.8;
        ++begin;
    }
}

using var_t = std::variant<float*, int*, double*>;

// I am just going to assume the type in input is *float to save lines
template<class UnaryOperator>
void my_function(UnaryOperator unary_op, var_t input, size_t size)
{
    if (input.index() != 0)
        return;
    float* begin = std::get<0>(input);
    float* end = begin + size;
    unary_op<float>(begin, end);
}

int main() 
{
    float* vals = new float[](10);
    my_function(execute_example, vals, 10); // works!!
    delete[] vals;
}

Even if I changed my_function to the following, it still wouldn't work.

template<typename T>
void my_function(std::function<void(T*,T*)> unary_op, var_t input, size_t size)

Is there a way to do this? It seems like there should be because the following is also valid:

template<class UnaryOperator>
void my_function(UnaryOperator&& unary_op, var_t input, size_t size) 
{
    if (input.index() != 0)
        return;
    float* begin = std::get<0>(input);
    float* end = begin + size;
    std::forward<UnaryOperator>(unary_op)(begin, end);
}

int main()
{
    float* vals = new float[10];
    my_function([](auto* a, auto* b) {
        typedef typename std::remove_pointer<decltype(a)>::type value_t;
        value_t val = 10 * 0.8;
        while (a != b) {
            *a = val;
            val *= 0.8;
            ++a;
        }
    }, vals, 10);
    delete[] vals;
}

Which would do the same thing, and I would not have to specify a type.

JeJo
  • 30,635
  • 6
  • 49
  • 88
Sam Moldenha
  • 463
  • 2
  • 11
  • How does this while loop ever terminate: `while(begin != end)`? Shouldn't you check `<` instead since you are multiplying the `begin` pointer by some odd 0.8? Might be a noob observation on my part but I don't understand how this works.. – Eldinur the Kolibri Jul 27 '23 at 21:13
  • 2
    @EldinurtheKolibri If you run the code it terminates, that function is meant to serve as a small example, and is not really pertinent to my question. However, the line that says `++begin` increments the `begin` pointer, and then it eventually reaches the pointer declared as `end`. It is how most iterators work, I just happened to use a pointer which is entirely valid. I recommend you run the code and see that it terminates. You should look into iterators, and look at the documentation on `std::for_each` it follows a similar format. Also maybe watch some youtube videos on pointer arithmetic :) – Sam Moldenha Jul 27 '23 at 21:16
  • See https://en.cppreference.com/w/cpp/language/template_parameters section Template Template Parameters – ALX23z Jul 27 '23 at 21:21
  • The example doesn't make too much sense since you are only using one fixed branch of the variant, You could just use `float * input` instead and make `my_function` a non-template. – n. m. could be an AI Jul 28 '23 at 05:09
  • @n.m.willseey'allonReddit that is why I wrote the comment `//I am just going to assume the type in input is *float to save lines` I wrote that purely for the readability. in practice I would use it for all the different types within a variant though. – Sam Moldenha Jul 28 '23 at 06:03
  • If you do it for different types, then perhaps you can extract the action for each type to a separate function or a separate specialisation of a function template? Something like `if (input.index() == 0) process(std::get<0>(input), size); else if...` etc? – n. m. could be an AI Jul 28 '23 at 07:54

2 Answers2

4

A function template is just a blueprint to the set of function, which caller may institute at some point. Until it hasn't been instantiated with congregate type, there you can not undress anything and pass the pointer to the function of it, to anywhere.

Additionally, the issue stays the same, even when you use the std::function as well. All it needs is an explicit template argument. I suggest the following, which also doesn't required the std::variant.

template<class UnaryOperator, typename T>
void my_function(UnaryOperator unary_op, T* input, size_t size) ,
{
    unary_op(input, input + size);
}

int main() 
{
    // use std::vector<float> or smart pointers here (If applicable)
    float* vals = new float[10]{}; 

    my_function(&execute_example<float>, vals, 10);

    delete[] vals;
}

Or make the execute_example as a generic lambda

inline static constexpr auto execute_example = [](auto* begin, auto* end)
{
    // .....
};

template<class UnaryOperator, typename T>
void my_function(UnaryOperator unary_op, T* input, size_t size) 
{
    unary_op(input, input + size);
}

int main() 
{
    float* vals = new float[10] {};

    // Use std::vector<float> or smart pointers here (if applicable)
    my_function(execute_example, vals, 10);

    delete[] vals;
}

See a demo in godbolt.org

JeJo
  • 30,635
  • 6
  • 49
  • 88
  • `float* vals = new float[10]{}; ... delete[] vals;` can be simplified to just `float vals[10]{};` in this example. – Remy Lebeau Jul 28 '23 at 01:53
  • @RemyLebeau Even better would be using `std::array` or `std::vector`. Since it is not clear, what actually the use case of OP is, I didn't simplify this part further in the answer. – JeJo Jul 28 '23 at 05:24
1

You can wrap the function template in a generic lambda. I.e. rewrite this line

my_function(execute_example, vals, 10);

to this:

my_function([](auto* begin, auto* end) {
    execute_example(begin, end);
}, vals, 10);

The call operator operator() is a template, but the lambda itself has a concrete type. Thats why you can pass it as an argument to another function.

chrysante
  • 2,328
  • 4
  • 24