使用可变模板的隐式转换

14

考虑两个函数调用

foo({"a", 1}, {"b", "value"});
foo({"a", 1}, {"b", "value"}, {"c", 1.0});

有没有一种方法可以编写一个函数 foo,用于任意数量的参数对?

我在考虑以下方式:

template <typename... Args>
void foo(std::pair<const char*, Args>&&...);

不幸的是,它并没有起作用。

gcc 报错:

error: too many arguments to function 'void foo(std::pair<const char*, Args>&& ...) [with Args = {}]'
 foo({"aa", 1});
4个回答

8

请尝试简化您的示例,并考虑以下内容:

#include<utility>

template<typename T>
void foo(std::pair<char*, T>) {}

int main() {
    foo({"a", 1});
}

它没有编译成功,如您所见
问题在于{ "a", 1 }不是std::pair,尽管您可以按照以下方式从中构建一个:

#include<utility>

void foo(std::pair<char*, int>) {}

int main() {
    foo({"a", 1});
}

错误很明显:
无法推断模板参数'T'。
为什么不能呢?
一旦知道T,编译器就可以构造这样的一对。不管怎样,T必须被推导出来,而编译器无法推导出来,因为{“a”,1}不是可以从中推导出它的一对。
无论如何,{“a”,1}都可以转换为一对,在特定情况下转换为std::pair<char*,T>的专业化,但首先必须推导出T。
从哪里推导?当然是一对,但你还没有一对。
以此类推,进入一个循环。
现在让我们讨论一下您尝试做类似事情的可变模板的情况:不用说,如果甚至上面显示的更简单的示例都无法编译,则其可变扩展(如果有)也将由于更多或更少相同的原因而无法编译。

是否有办法为任意数量的参数对编写函数foo?

我会说不行,除非你将一对作为foo的参数。
以下是一个最小的工作示例:
#include<utility>

template <typename... Args>
void foo(std::pair<const char*, Args>&&...) {}

int main() {
    foo(std::make_pair("a", 1), std::make_pair("b", "value"));
}

如果您愿意,您也可以推断出第一个参数,只要它的类型是固定的:

#include<utility>

template <typename T, typename... Args>
void foo(std::pair<T, Args>&&...) {}

int main() {
    foo(std::make_pair("a", 1), std::make_pair("b", "value"));
}

否则,如果问题没有得到解决,你可以尝试以下方法:
#include<utility>

template <typename... First, typename... Second>
void foo(std::pair<First, Second>&&...) {}

int main() {
    foo(std::make_pair("a", 1), std::make_pair(0, "value"));
}

7
有没有一种方法可以编写函数foo,使其适用于任意数量的参数对?
有一些基于可变模板的解决方案,但是参数必须是成对出现的,以便让编译器能够推断类型。然后像这样的代码可能有效:
template<typename... Args>
void foo() {}

template<typename T, typename U, typename... Args>
void foo(const std::pair<T, U>& p, Args... args) {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
    foo(args...);
}

所以,针对:
foo(std::make_pair("a", 1), std::make_pair("b", "value"), std::make_pair("c", 1.0));

使用 clang 3.8 编译的输出结果如下:

void foo(const std::pair<T, U> &, Args...) [T = const char *, U = int, Args = <std::__1::pair<const char *, const char *>, std::__1::pair<const char *, double>>]
void foo(const std::pair<T, U> &, Args...) [T = const char *, U = const char *, Args = <std::__1::pair<const char *, double>>]
void foo(const std::pair<T, U> &, Args...) [T = const char *, U = double, Args = <>]

这里 是完整的工作示例。


1
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Edgar Rokjān
1
@EdgarRokyan __PRETTY_FUNCTION__ - skypjack
此外,您不必逐个从包中获取配对。请参见此答案中的最后一段代码片段。它与OP在问题中发布的内容有些相似。我认为他对“Args”感兴趣。 - skypjack
@skypjack,我已经检查过了,你的解决方案很好!我们能否对其进行修改,以避免在这些对中将第一个类型硬编码为“const char *”? - Edgar Rokjān
@EdgarRokyan 添加了另一个片段,以防第一个参数不是固定类型。;-) - skypjack
显示剩余4条评论

4

稍微解释一下Edgar Rokyan的回答,你可以将对创建成对的操作移到foo函数中:

template<typename... Args>
void foo() {}

// Forward declaration
template<typename U, typename... Args>
void foo(const char * str, U u, Args... args);

// When given a pair
template<typename U, typename... Args>
void foo(const std::pair<const char *, U>& p, Args... args) {
    std::cout << p.first << " = " << p.second << std::endl;
    foo(args...);
}

// when given a C string and something else, make a pair
template<typename U, typename... Args>
void foo(const char * str, U u, Args... args) {
    foo(std::make_pair(str, u), args...);
}

然后你可以这样调用它:
foo("hi", 42,
    "yo", true,
    std::make_pair("Eh", 3.14),
    "foo", false,
    some_pair);

请在 foo 的第一个重载声明上方删除 template<typename...Args>(即删除第一行)。您没有调用任何 foo<arg...>(),只是调用了 foo()。将第一个空的 foo 重载声明为模板只会带来混乱:它看起来像是模板特化和重载之间存在混淆。在您的代码中,您声明了三个 foo 重载,但只使用了第一个重载的一个特化:foo<>(),因此第一个重载不需要是一个模板。 - Oliv
更正:仅使用第一个重载的一个实例:foo<>()。 - Oliv

0
在C++17中,您可以通过使用构造函数的模板推导来绕过问题并欺骗编译器:
#include <iostream>
#include <utility>

template <class... Args>
struct P:std::pair<Args...> {
    P(Args... args):std::pair<Args...>(args...) { }
};

template <class... Args>
void foo(std::pair<const char *, Args>&&...) {
}

int main() {
    foo(P{"abc", 1}, P{"abc", "abc"}, P{"abc", 2.0});
}

【实时演示】


1
如果你可以从构造函数中推断出类型,那么你可以写成 foo(std::pair{"a", 1}/*...*/). 如果你不想写这样一个“长”的名称 std::pair,那么可以写成 template<typename T1, typename T2> using P = std::pair<T1, T2>。不需要使用 struct P 这样的东西。 - Rostislav
@Rostislav 嗯,也许在c++17中它可以工作,但现在gcc不允许它:示例 - W.F.
这很有趣,为什么这个额外的层对于字符串字面值运作良好,但是直接的 pair 构造不行(似乎对于非字符串字面值也能正常工作)。 - Rostislav
@Rostislav 我不会责怪gcc,因为它还没有完全支持c++17 :) - W.F.
当然,编译器是非常复杂的,而且有一些技巧可以让一些事情实现,真是令人惊叹 :) - Rostislav
1
@Rostislav 类模板参数推导无法与别名模板一起使用。 - Oktalist

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接