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.

6
  • 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.. Commented Jul 27, 2023 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 :) Commented Jul 27, 2023 at 21:16
  • See en.cppreference.com/w/cpp/language/template_parameters section Template Template Parameters Commented Jul 27, 2023 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. Commented Jul 28, 2023 at 5: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. Commented Jul 28, 2023 at 6:03

2 Answers 2

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

Sign up to request clarification or add additional context in comments.

2 Comments

float* vals = new float[10]{}; ... delete[] vals; can be simplified to just float vals[10]{}; in this example.
@RemyLebeau Even better would be using std::array<type, 10> or std::vector<type>. Since it is not clear, what actually the use case of OP is, I didn't simplify this part further in the answer.
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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.