Некоторое время назад я прочитал статью, в которой объяснялось несколько ошибок, зависящих от аргументов, но я больше не могу их найти. Речь шла о том, чтобы получить доступ к вещам, к которым у вас не должно быть доступа или что-то в этом роде. Поэтому я подумал, что я бы спросил здесь: каковы подводные камни 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 в действии.)
Да, зависящий от аргументов поиск является важной особенностью С++. По существу требуется добиться желаемого поведения некоторых языковых функций, таких как перегруженные операторы (рассмотрим библиотеку потоков). Тем не менее, это также очень, очень ошибочно и может привести к действительно уродливым проблемам. Было несколько предложений по исправлению зависимого от аргументов поиска, но ни один из них не был принят комитетом стандартов С++.