Почему компилятор не дает двусмысленной ошибки ссылки?
Что касается следующего кода
#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)
)
Мне просто интересно, почему оба компиляторы не просто говорят, что ссылка неоднозначна. Я чувствую, что это как-то связано с тем, как экземпляры шаблонов работают на С++, но я не уверен в конкретной причине.
Ответы
Ответ 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)
является ложным.