После вопроса Как я могу определить, может ли тип передаваться в 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 до объявление черты, оно печатает правда.
Так почему же происходит эта странная вещь и как я могу исправить эту проблему?
Как упоминал Якк, это просто 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;
}