Почему мой оператор поиска классов шаблонов черт & lt; & lt; для llvm :: StringRef?

После вопроса Как я могу определить, может ли тип передаваться в std :: ostream? Я написал класс черты, который говорит, что какой-то тип может быть передан потоку ввода-вывода. Эта черта, казалось, работала хорошо до сих пор, когда я обнаружил проблему.

Я использую код внутри проекта, который использует LLVM, и я использую их класс StringRef (который по духу похож на предложенный std :: string_view). Вот это ссылка на документ Doxygen для класса, откуда вы можете найти его заголовочный файл объявления, если это необходимо. Поскольку LLVM не предоставляет оператора<< для потоковой передачи объектов StringRef в потоки std (они используют собственный облегченный класс потока), я написал один.

Однако, когда я использую черту, она не работает, если мой пользовательский оператор<< объявлен после черта (это происходит потому, что у меня есть черта в одном заголовке и оператор<< функция в другом). Раньше я думал, что поиск в экземплярах шаблона работает с точки зрения точки создания, поэтому я подумал, что это должно работать. На самом деле, как вы можете видеть ниже, с другим классом и его пользовательским оператором<<, объявленный после черты, все работает как положено (вот почему я обнаружил эту проблему только сейчас), поэтому я не могу понять, что делает StringRef особенным.

Это полный пример:

#include <iostream>

#include "llvm/ADT/StringRef.h"
// Trait class exactly from the cited question's accepted answer
template<typename T>
class is_streamable
{
template<typename SS, typename TT>
static auto test(int)
-> decltype(std::declval<SS&>() << std::declval<TT>(),
std::true_type());

template<typename, typename>
static auto test(...) -> std::false_type;

public:
static const bool value = decltype(test<std::ostream,T>(0))::value;
};

// Custom stream operator for StringRef, declared after the trait
inline std::ostream &operator<<(std::ostream &s, llvm::StringRef const&str) {
return s << str.str();
}

// Another example class
class Foo { };
// Same stream operator declared after the trait
inline std::ostream &operator<<(std::ostream &s, Foo const&) {
return s << "LoL\n";
}

int main()
{
std::cout << std::boolalpha << is_streamable<llvm::StringRef>::value << "\n";
std::cout << std::boolalpha << is_streamable<Foo>::value << "\n";

return 0;
}

Вопреки моим ожиданиям, это печатает:

false
true

Если я переместить декларацию оператора<< для StringRef до объявление черты, оно печатает правда.
Так почему же происходит эта странная вещь и как я могу исправить эту проблему?

5

Решение

Как упоминал Якк, это просто ADL: Аргумент-зависимый поиск.

Если вы не хотите беспокоиться, просто помните, что вы всегда должны писать свободную функцию в том же пространстве имен, что и хотя бы один из ее аргументов. В вашем случае, так как запрещено добавлять функции в stdэто означает добавление вашей функции в llvm Пространство имен. Тот факт, что вам нужно было квалифицировать StringRef спорить с llvm:: был мертвым отдать.

Правила разрешения функций довольно сложны, но для краткости:

  • поиск имени: собирает набор потенциальных кандидатов
  • разрешение перегрузки: выбирает лучшего кандидата среди потенциалов
  • разрешение специализации: если кандидат является шаблоном функции, проверьте наличие любой специализации, которая может применяться

Фаза поиска имени, с которой мы здесь имеем дело, относительно проста. Короче:

  • он сканирует пространства имен аргумента, затем их родителей, … пока не достигнет глобальной области видимости
  • затем сканирует текущую область, затем родительскую область, … пока не достигнет глобальной области

Наверное разрешить слежку (как и для любого другого поиска имени), поиск останавливается на первой области, в которой он встречает совпадение, и высокомерно игнорирует любую окружающую область.

Обратите внимание, что using директивы (using ::operator<<; например) может использоваться для ввода имени из другой области. Однако это обременительно, поскольку возлагает ответственность на клиента, поэтому, пожалуйста, не полагайтесь на его доступность в качестве оправдания небрежности (что я уже видел: x).


Пример слежка: это печатает "Hello, World" без возникновения ошибки двусмысленности.

#include <iostream>

namespace hello { namespace world { struct A{}; } }

namespace hello { void print(world::A) { std::cout << "Hello\n"; } }

namespace hello { namespace world { void print(A) { std::cout << "Hello, World\n"; } } }

int main() {
hello::world::A a;
print(a);
return 0;
}

Пример прерванный поиск: ::hello::world дал функцию с именем print поэтому он был выбран, даже если он не совпадает и ::hello::print был бы строго лучший матч.

#include <iostream>

namespace hello { namespace world { struct A {}; } }

namespace hello { void print(world::A) { } }

namespace hello { namespace world { void print() {} } };

int main() {
hello::world::A a;
print(a); // error: too many arguments to function ‘void hello::world::print()’
return 0;
}
1

Другие решения


По вопросам рекламы [email protected]