Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use of C's '...' parameter pack as C++ template value?

I have spent the last few days trying to create a generalised wrapper of sorts for function pointers in C++ and I've managed to solve nearly every single issue bar one. My main goal with this was to be able to simply call an object as a function with a function pointer stored internally. If the pointer was pointing somewhere then it would call it as normal while a null pointer would just not call the function and it would continue on as if nothing happened. I intend to use this primarily for callback function purposes, where I would likely not care if the function was called or not and just wanted to preform an action. It works almost perfectly with the following:

template<typename T>
class Action;

template<typename TReturn, typename ... TArgs>
class Action<TReturn(TArgs...)> {
public:
    //! Define a type for the Action object
    typedef TReturn(*signature)(TArgs...);

    //! Constructors
    inline Action(const signature& pFunc = nullptr) : mPointer(pFunc) {}
    inline Action(const Action& pCopy) : mPointer(pCopy.mPointer) {}

    //! Operator Call
    inline bool operator() (TReturn& pReturn, TArgs ... pArgs) const { if (!mPointer) return false; pReturn = mPointer(pArgs...); return true; }

    //! Operators
    inline Action& operator=(const Action& pCopy) { mPointer = pCopy.mPointer; return *this; }
    inline Action& operator=(const signature& pFunc) { mPointer = pFunc; return *this; }
    inline operator bool() const { return (mPointer != nullptr); }

private:
    //! Store a pointer to the callback function
    signature mPointer;
};

template<typename ... TArgs>
class Action<void(TArgs...)> {
public:
    //! Define a type for the Action object
    typedef void(*signature)(TArgs...);

    //! Constructors
    inline Action(const signature& pFunc = nullptr) : mPointer(pFunc) {}
    inline Action(const Action& pCopy) : mPointer(pCopy.mPointer) {}

    //! Operator Call
    inline bool operator() (TArgs ... pArgs) const { if (!mPointer) return false; mPointer(pArgs...); return true; }

    //! Operators
    inline Action& operator=(const Action& pCopy) { mPointer = pCopy.mPointer; return *this; }
    inline Action& operator=(const signature& pFunc) { mPointer = pFunc; return *this; }
    inline operator bool() const { return (mPointer != nullptr); }

private:
    //! Store a pointer to the callback function
    signature mPointer;
};

However, I feel like the situation that would most likely use this wrapper is the output of debug information or formatted text. This could be through user defined functions or inbuilt functions such as printf. To match with printf's signature an Action would be created like:

Action<int(const char*, ...)> callback = printf;

and it would be able to operate the same way that any other Action would behave. The problem I'm finding is the '...' will force the template signature to not align with either of the specialisations, instead going with the first which is only a prototype.

I can fully understand why this doesn't work and why the compiler would not be able to handle the generation of the required class but I was hoping that someone here would know any sneaky ways to either achieve this or something similar. Any help would be much appreciated, thanks :)

like image 576
MitchellCroft Avatar asked Jul 11 '17 06:07

MitchellCroft


People also ask

What is a template parameter C++?

A template parameter is a special kind of parameter that can be used to pass a type as argument: just like regular function parameters can be used to pass values to a function, template parameters allow to pass also types to a function.

What is a template parameter?

In UML models, template parameters are formal parameters that once bound to actual values, called template arguments, make templates usable model elements. You can use template parameters to create general definitions of particular types of template.

Which of the following are valid reasons for using Variadic templates in C++?

Variadic templates are class or function templates, that can take any variable(zero or more) number of arguments. In C++, templates can have a fixed number of parameters only that have to be specified at the time of declaration. However, variadic templates help to overcome this issue.

What is Typename C++?

" typename " is a keyword in the C++ programming language used when writing templates. It is used for specifying that a dependent name in a template definition or declaration is a type.


1 Answers

The following example works for all function types, and lambdas with no capture:

#include <utility>
#include <cstdio>
#include <cmath>

template<typename Fn>
class Action {
    Fn* function_ptr;        
public:
    Action() noexcept : function_ptr(nullptr) {}    
    Action(std::nullptr_t) noexcept : function_ptr(nullptr) {}    
    Action(const Action& other) : function_ptr(other.function_ptr) {}    
    Action(Fn f) : function_ptr(f) {}

    Action& operator=(const Action& other) {
        return (function_ptr = other.function_ptr, *this);
    }            
    Action& operator=(std::nullptr_t ) {
        return (function_ptr = nullptr, *this);
    }    
    Action& operator=(Fn f) {
        return (function_ptr = f, *this);
    }

    template<typename... Params>
    auto operator()(Params&&... params) {
        return function_ptr(std::forward<Params>(params)...);
    }
};

Live Demo

As the comments under your question mention, using std::function is better than writing a wrapper for function pointers. The only problem with std::function is that it cannot be used with function signatures that contain ellipsis. If you explicitly specify the signature you can store e.g. printf as follows:

std::function<int(const char*, int, double, double)> fn = printf;

If you use C++17 or Boost, you can implement you own printf-like function, using std::any or Boost.Any which can be assigned to std::function as follows:

#include <iostream>
#include <string>
#include <vector>
#include <any>
#include <functional>

using namespace std;

void any_printf(string&& format, vector<any>&& args) {
    int arg_index = 0; enum { NORMAL, CONVERT } mode;

    for(auto& chr : format) {
        if(mode == CONVERT) {
            switch(chr) {
            case 'd': cout << any_cast<int>(args[arg_index++]); break;
            case 'f': cout << any_cast<float>(args[arg_index++]); break;
            case 's': cout << any_cast<string>(args[arg_index++]); break;
            /* ... */
            default: cout << chr;
            };
            mode = NORMAL;
        }
        else {
            chr == '%' ? (mode = CONVERT, 0) : (cout << chr, 0);
        }
    }
}

int main() {
    using namespace string_literals;
    function<void(string&&, vector<any>&&)> f_ptr { any_printf };

    f_ptr("Cuboid: %smm x %dmm x %fmm.\n", { any("3"s), any(4), any(6.67f) });
    return 0;
}
like image 65
Akira Avatar answered Oct 13 '22 19:10

Akira