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

Posted

技术标签:

【中文标题】如何模板化一个接受模板化参数并在 C++ 中对其应用模板化函数的函数?【英文标题】:How do I template a function that takes templated args and applies a templated function on them in c++? 【发布时间】:2015-11-16 05:25:53 【问题描述】:

我有一堆静态类函数,它们接受不同数量的 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

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

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 以及如何别名或定义静态函数。有一些不太优雅的解决方案,但它们需要我想避免的重复样板。

【问题讨论】:

只要给它们都一样的名字(即重载,不要模板)。 Für C++11,看看可变参数模板。但是,我没有检查它是否可以完美满足您的所有要求 @n.m. - 我不能把它们都命名为相同的东西;对于不同的情况,相同的签名可能具有不同的语义(例如,有多种情况只采用一个 int,但对输出执行不同的操作)。 【参考方案1】:

有了一个类,你可以这样做:

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

【讨论】:

疯狂的模板魔法,但大概一开始问题就变得不必要地复杂了。为什么不简单地重载相同的函数,类似于std::to_string() @Jarod42 使用 c++11 编译此代码会给我错误“'int(int, struct Output*)' is not a valid type for a template non-type parameter using StringOutput1 = OutputString;"我错过了什么? @Walter 它不能被重载,因为 ChangeOutput3 和 ChangeOutput4 具有相同的签名 @Jarod42 好的,发现了。 decltype 不执行指向函数衰减的指针,因此“使用”行必须是 decltype(&ChangeOutput1)... @LoPiTaL:添加了演示,并修复了编译。【参考方案2】:

如果将输出参数移到前面,则可以这样做。

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

和“函数别名”看起来像这样:

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>

然后用宏消除重复,但这很丑。

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

【讨论】:

以上是关于如何模板化一个接受模板化参数并在 C++ 中对其应用模板化函数的函数?的主要内容,如果未能解决你的问题,请参考以下文章

C++模板详解:泛型编程模板原理非类型模板参数模板特化分离编译

C++模板详解:泛型编程模板原理非类型模板参数模板特化分离编译

函数模板参数的C++模板实例化

C++从青铜到王者第六篇:C++模板初阶

C++ Primer 5th笔记(chap 16 模板和泛型编程)类模板部分特例化

C++模板类模板的全部特例化和局部特例化(偏特化-partial specialization)