使用 C 的“...”参数包作为 C++ 模板值?

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

过去几天我一直在尝试为 C++ 中的函数指针创建一个通用的包装器,并且我已经设法解决了几乎所有问题,除了一个。我的主要目标是能够简单地将对象作为函数调用,并在内部存储函数指针。如果指针指向某处,那么它会正常调用它,而空指针不会调用该函数,它会继续运行,就好像什么也没发生一样。我打算主要将其用于回调函数目的,我可能不关心该函数是否被调用,而只是想执行一个操作。它几乎可以与以下内容完美配合:

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;
};

但是,我觉得最有可能使用这个包装器的情况是调试信息或格式化文本的输出。这可以通过用户定义的函数或内置函数(如 printf)实现。为了匹配 printf 的签名,将创建一个 Action,如:

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

并且它将能够以与任何其他 Action 相同的方式运行。我发现的问题是“...”将强制模板签名不与任何一个专业化对齐,而是与第一个只是原型的签名对齐。

我完全可以理解为什么这不起作用以及为什么编译器无法处理所需的生成 class 但我希望这里有人知道任何偷偷摸摸的方法来实现这个或类似的东西。任何帮助将不胜感激,谢谢:)

以下示例适用于所有函数类型和没有捕获的 lambda:

#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

正如您问题下的评论所述,使用 std::function 比为函数指针编写包装器要好。 std::function 的唯一问题是它不能与包含省略号的函数签名一起使用。如果您明确指定签名,您可以存储例如printf如下:

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

如果你使用 C++17 或 Boost,你可以实现你自己的 printf-like 函数,使用 std::any or Boost.Any 可以分配给 std::function 如下:

#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;
}