为什么编译器不会给出模糊的引用错误?

时间:2017-06-07 17:46:45

标签: c++ c++11 templates gcc clang

参考以下代码

#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 ++中的工作方式有关,但我不确定其确切原因。

2 个答案:

答案 0 :(得分:7)

我认为编译器是正确的,但它可能很奇怪。模板参数推导规则与替换规则不同。模板参数包上下文中重载函数解析的模糊性并不一定意味着失败。

请参阅[temp.deduct.call]/p6

  

当P是函数类型,函数指针类型或指向成员的指针时   功能类型:

     

...

     

- 如果参数是重载集(不包含函数模板),则尝试使用每个参数进行试验推论   成员。       如果仅对其中一个重载集成员进行推导成功,则该成员将用作推导的参数值。       如果对重载集的多个成员进行推导成功,则该参数将被视为非推导的上下文。

因此,对于参数包的最后一个参数,我们处于非推导的上下文中(而不是错误)。

[temp.arg.explicit]/p3

  

...未以其他方式推导出的尾随模板参数包将被推导为空的模板参数序列。 ...

所以在我看来(虽然最后一点并没有明确地说明部分推断出的参数包),但是在演绎阶段,模糊函数指针被简单地抛弃,随后替换失败,因为它试图替换推导出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。