参考以下代码
#include <iostream>
#include <tuple>
#include <string>
#include <type_traits>
using std::cout;
using std::endl;
using std::string;
template <typename... Args>
void bar(Args&&...) {}
int change(const string&) { return 1; }
double change(int) { return 1.0; }
int main() {
// bar(1, 2.0, static_cast<int(*)(const string&)>(&change));
bar(1, 2.0, &change);
return 0;
}
我理解上面代码中的错误是对change
函数的引用是不明确的(这就是注释行有效的原因),但是为什么编译器会给出这个错误消息?
test.cpp:17:5: error: no matching function for call to 'bar'
bar(1, 2.0, &change);
^~~
test.cpp:11:6: note: candidate function not viable: requires 2 arguments, but 3 were
provided
void bar(Args&&...) {}
^
1 error generated.
在gcc(&gt; 5)和clang(Apple LLVM version 8.0.0 (clang-800.0.42.1)
)上都会发生这种情况
我只是好奇为什么两者编译器不只是说引用是不明确的。我觉得它与模板实例化在C ++中的工作方式有关,但我不确定其确切原因。
答案 0 :(得分:7)
我认为编译器是正确的,但它可能很奇怪。模板参数推导规则与替换规则不同。模板参数包上下文中重载函数解析的模糊性并不一定意味着失败。
当P是函数类型,函数指针类型或指向成员的指针时 功能类型:
...
- 如果参数是重载集(不包含函数模板),则尝试使用每个参数进行试验推论 成员。 如果仅对其中一个重载集成员进行推导成功,则该成员将用作推导的参数值。 如果对重载集的多个成员进行推导成功,则该参数将被视为非推导的上下文。
因此,对于参数包的最后一个参数,我们处于非推导的上下文中(而不是错误)。
...未以其他方式推导出的尾随模板参数包将被推导为空的模板参数序列。 ...
所以在我看来(虽然最后一点并没有明确地说明部分推断出的参数包),但是在演绎阶段,模糊函数指针被简单地抛弃,随后替换失败,因为它试图替换推导出2个参数函数的3个参数。当它需要解决歧义时,它永远不会达到一定程度。
答案 1 :(得分:3)
贾斯汀是对的。通过调试器运行GCC会导致这些代码行:
cp_parser_lookup_name(cp_parser*, tree_node*, tag_types, bool, bool, bool, tree_node**, unsigned int) () at ../../gcc/cp/parser.c:24665
24665 {
(gdb)
24667 tree object_type = parser->context->object_type;
(gdb)
24670 if (ambiguous_decls)
(gdb)
24665 {
(gdb)
24667 tree object_type = parser->context->object_type;
(gdb)
24670 if (ambiguous_decls)
(gdb)
24676 parser->context->object_type = NULL_TREE;
...
(gdb) list 24670
24665 {
24666 tree decl;
24667 tree object_type = parser->context->object_type;
24668
24669 /* Assume that the lookup will be unambiguous. */
24670 if (ambiguous_decls)
24671 *ambiguous_decls = NULL_TREE;
24672
24673 /* Now that we have looked up the name, the OBJECT_TYPE (if any) is
24674 no longer valid. Note that if we are parsing tentatively, and
这是发出诊断的实际代码:
6914 complain);
(gdb)
test.cpp:9:24: error: too many arguments to function ‘void bar(Args&& ...) [with Args = {}]’
bar(1, 2.0, &change);
^
test.cpp:2:6: note: declared here
void bar(Args&&...) {}
...
(gdb) list 6914
6909 /* All other function calls. */
6910 postfix_expression
6911 = finish_call_expr (postfix_expression, &args,
6912 /*disallow_virtual=*/false,
6913 koenig_p,
6914 complain);
6915
6916 if (close_paren_loc != UNKNOWN_LOCATION)
6917 {
6918 location_t combined_loc = make_location (token->location,
跳过一堆东西(因为它会使这个答案不必要地长),在重载解析过程中会发生实际错误:
(gdb)
add_candidates (fns=0x7fffeffb0940, first_arg=first_arg@entry=0x0, args=args@entry=0x7fffeff9baf0, return_type=return_type@entry=0x0, explicit_targs=0x0,
template_only=false, conversion_path=0x0, access_path=0x0, flags=1, candidates=0x7fffffffd320, complain=3) at ../../gcc/cp/call.c:5302
5302 for (; fns; fns = OVL_NEXT (fns))
(gdb)
5365 }
(gdb)
perform_overload_resolution (complain=3, any_viable_p=<synthetic pointer>, candidates=0x7fffffffd320, args=0x7fffeff9baf0, fn=<optimized out>)
at ../../gcc/cp/call.c:4036
4036 *candidates = splice_viable (*candidates, false, any_viable_p);
(gdb)
build_new_function_call(tree_node*, vec<tree_node*, va_gc, vl_embed>**, bool, int) () at ../../gcc/cp/call.c:4111
4111 complain);
(gdb)
4115 if (complain & tf_error)
(gdb)
4119 if (!any_viable_p && candidates && ! candidates->next
(gdb)
4120 && (TREE_CODE (candidates->fn) == FUNCTION_DECL))
(gdb)
4121 return cp_build_function_call_vec (candidates->fn, args, complain);
错误发生在convert_arguments
:
(gdb) list 3611
3606 allocated = make_tree_vector ();
3607 params = &allocated;
3608 }
3609
3610 nargs = convert_arguments (parm_types, params, fndecl, LOOKUP_NORMAL,
3611 complain);
3612 if (nargs < 0)
3613 return error_mark_node;
3614
3615 argarray = (*params)->address ();
最后,诊断在error_num_args
中发出,因为if (TREE_CODE (TREE_TYPE (fndecl)) == METHOD_TYPE)
为false。