如何在 C++ 中模板化接受模板化参数并在其上应用模板化函数的函数?

How do I template a function that takes templated args and applies a templated function on them in c++?

我有一堆静态 class 函数,它们接受不同数量的 {string, int, float} 参数和一个输出参数。根据所调用的函数,相同的参数可能会有不同的行为。例如:

static void ChangeOutput1(const string& foo, int bar, Output* output);
static void ChangeOutput2(int bar, Output* output);
static void ChangeOutput3(float foo, Output* output);
static void ChangeOutput4(float foo, Output* output);  // behaves differently from ChangeOutput3

我想要一种简单、安全的方式来编写模板以对每个函数执行类似的行为,基本上是使用 Output 参数调用并将其转换为字符串 return。理想情况下无需再次指定参数类型。它可能看起来像这样:

template<typename... Args, int (*ChangeOutputFn)(Args...)>
string OutputString(Args... args) {
    Output output;
    ChangeOutputFn(args..., &output);
    return ConvertToString(output);
}

// Is there a way to alias static templated functions?
using StringOutput1 = OutputString<ChangeOutput1>;
using StringOutput2 = OutputString<ChangeOutput2>;
using StringOutput3 = OutputString<ChangeOutput3>;

我不确定如何实现。我不确定如何编写 OutputString 以及我将如何别名或定义静态函数。有不太优雅的解决方案,但它们需要我想避免的重复样板。

对于 class,您可以这样做:

template <typename T, T f> struct OutputString;

template<typename... Args, void (*ChangeOutputFn)(Args...)>
struct OutputString<void (*)(Args...), ChangeOutputFn>
{
    template <typename ... Ts>
    auto operator()(Ts... args)
    -> decltype(ChangeOutputFn(std::forward<Ts>(args)..., std::declval<Output *>()),
                std::string{})
    {
        Output output;
        ChangeOutputFn(std::forward<Ts>(args)..., &output);
        return ConvertToString(output);
    }
};

然后

using StringOutput1 = OutputString<decltype(&ChangeOutput1), &ChangeOutput1>;
using StringOutput2 = OutputString<decltype(&ChangeOutput2), &ChangeOutput2>;
using StringOutput3 = OutputString<decltype(&ChangeOutput3), &ChangeOutput3>;

并将其用作

std::string s2 = StringOutput2{}(42);
std::string s3 = StringOutput3{}(4.2f);

Demo

如果将 Output 参数移到前面,就可以做到这一点。

static void ChangeOutput1(Output*, const std::string& foo, int bar);
static void ChangeOutput2(Output*, int bar);
static void ChangeOutput3(Output*, float foo);
static void ChangeOutput4(Output*, float foo);

现在你可以拥有这个模板了:

template<typename... Args>
std::function<std::string(Args...)>
mkOutput (void (*ChangeOutputFn)(Output*, Args...))
{
    return [ChangeOutputFn](Args... args)->std::string{
        Output output;
        ChangeOutputFn(&output, args...);
        return ConvertToString(output);
    };
}

和"function aliases"看起来像这样:

auto a1 = mkOutput(ChangeOutput1);
auto a2 = mkOutput(ChangeOutput2);
auto a3 = mkOutput(ChangeOutput3);
auto a4 = mkOutput(ChangeOutput4);

注意 1. 你不能有这个语法

OutputString<ChangeOutput1>

因为 ChangeOutput1 是一个值,而 OutputString 必须事先知道它的类型或将其作为另一个模板参数接收。

有这样的可能

OutputString<decltype(ChangeOutput1), ChangeOutput1>

然后用宏去掉重复的,但是那太丑了。

我选择在运行时而不是在编译时传递函数,这样更容易。