Подтвердить что ты не робот

Почему компилятор не дает двусмысленной ошибки ссылки?

Что касается следующего кода

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

Мне просто интересно, почему оба компиляторы не просто говорят, что ссылка неоднозначна. Я чувствую, что это как-то связано с тем, как экземпляры шаблонов работают на С++, но я не уверен в конкретной причине.

4b9b3361

Ответ 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, [email protected]=0x0, [email protected]=0x7fffeff9baf0, [email protected]=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) является ложным.