Некоторое время назад я прочитал статью, в которой объяснялось несколько ошибок, зависящих от аргументов, но я больше не могу их найти. Речь шла о том, чтобы получить доступ к вещам, к которым у вас не должно быть доступа или что-то в этом роде. Поэтому я подумал, что я бы спросил здесь: каковы подводные камни ADL?
Каковы подводные камни ADL?
Ответ 1
Существует огромная проблема с зависящим от аргумента поиска. Рассмотрим, например, следующую утилиту:
#include <iostream>
namespace utility
{
template <typename T>
void print(T x)
{
std::cout << x << std::endl;
}
template <typename T>
void print_n(T x, unsigned n)
{
for (unsigned i = 0; i < n; ++i)
print(x);
}
}
Это достаточно просто, не так ли? Мы можем вызвать print_n()
и передать ему любой объект, и он вызовет print
, чтобы напечатать объект n
раз.
На самом деле получается, что если мы только посмотрим на этот код, мы абсолютно не знаем, какую функцию вызывается print_n
. Это может быть шаблон функции print
, указанный здесь, но может и не быть. Зачем? Аргумент-зависимый поиск.
В качестве примера предположим, что вы написали класс для обозначения единорога. По какой-то причине вы также определили функцию с именем print
(какое совпадение!), Которое просто приводит к сбою программы, записывая нулевой указатель с разыменованием (кто знает, почему вы это сделали, что не важно):
namespace my_stuff
{
struct unicorn { /* unicorn stuff goes here */ };
std::ostream& operator<<(std::ostream& os, unicorn x) { return os; }
// Don't ever call this! It just crashes! I don't know why I wrote it!
void print(unicorn) { *(int*)0 = 42; }
}
Затем вы пишете небольшую программу, которая создает единорога и печатает его четыре раза:
int main()
{
my_stuff::unicorn x;
utility::print_n(x, 4);
}
Вы скомпилируете эту программу, запустите ее и... она сработает. "Что?!" - вы говорите: "Я только что позвонил print_n
, который вызывает функцию print
для печати единорога четыре раза!" Да, это правда, но он не вызвал функцию print
, которую вы ожидали от нее. Он называется my_stuff::print
.
Почему выбрано my_stuff::print
? Во время поиска имени компилятор видит, что аргумент вызова print
имеет тип unicorn
, который является типом класса, объявленным в пространстве имен my_stuff
.
Из-за зависящего от аргумента поиска компилятор включает это пространство имен в свой поиск кандидатных функций с именем print
. Он находит my_stuff::print
, который затем выбирается как лучший жизнеспособный кандидат во время разрешения перегрузки: для вызова любой из функций-кандидатов print
не требуется преобразование, а функции без шаблона предпочтительнее использовать шаблоны, поэтому функция без функции my_stuff::print
лучший матч.
(Если вы этого не верите, вы можете скомпилировать код в этом вопросе как есть и увидеть ADL в действии.)
Да, зависящий от аргументов поиск является важной особенностью С++. По существу требуется добиться желаемого поведения некоторых языковых функций, таких как перегруженные операторы (рассмотрим библиотеку потоков). Тем не менее, это также очень, очень ошибочно и может привести к действительно уродливым проблемам. Было несколько предложений по исправлению зависимого от аргументов поиска, но ни один из них не был принят комитетом стандартов С++.