【问题标题】:Why does the compiler not give an ambiguous reference error?为什么编译器不给出不明确的引用错误?
【发布时间】:2017-11-09 05:04:59
【问题描述】:

参考以下代码

#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 (>5) 和 clang (Apple LLVM version 8.0.0 (clang-800.0.42.1)) 上

我只是好奇为什么两个编译器不只是说引用不明确。我觉得这与模板实例化在 C++ 中的工作方式有关,但我不确定确切的原因。

【问题讨论】:

  • 我会说这是一个编译器错误
  • 有趣的是,在 VS2017 Intellisense 上会给出“正确”的错误消息,而编译器本身会给出与在 GCC 和 Clang 上观察到的相同的错误
  • @Justin 即使它是一个编译器错误,显然同样的错误出现在多个独立的编译器中。这意味着幕后可能会发生更有趣的事情。
  • 有可能,因为&amp;change 格式不正确,编译器将其视为“不是一个值”并做一些奇怪的事情。这可能会导致他们将模板实例化为bar&lt;int, double&gt;,因为“非值”不是类型,因此它没有插入到模板参数列表中,这意味着函数被调用时使用了太多参数。在 GCC 的情况下,看起来“不是值”使整个类型列表为空 (with Args = {})
  • 按照标准,当你输入一个格式错误的程序时,编译器可以给出任何诊断(甚至是空格!)。所以它们是符合标准的:但是,符合标准的错误消息有点太低了。 ;)

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


【解决方案1】:

我认为编译器是对的,尽管它可能很奇怪。模板参数推导规则不同于替换。模板参数包上下文中重载函数解析的歧义并不一定意味着失败。

[temp.deduct.call]/p6:

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

...

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

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

还有[temp.arg.explicit]/p3

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

所以在我看来(尽管最后一点没有明确说明部分推导的参数包)模棱两可的函数指针在推导阶段被简单地丢弃,随后替换失败,因为它试图替换 3 个参数成一个推导的 2 参数函数。它永远不会到达需要解决歧义的地步。

【讨论】:

    【解决方案2】:

    贾斯汀是对的。通过调试器运行 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) 为假。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-08-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-29
      • 1970-01-01
      • 2012-05-23
      相关资源
      最近更新 更多