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

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

Я использую код внутри проекта, который использует LLVM, и я использую их класс StringRef (который по духу похож на предложенный std::string_view). 1StringRef.html" rel="nofollow noreferrer">Вот ссылка на документацию Doxygen для класса, откуда вы можете найдите файл заголовка объявления, если это необходимо. Поскольку LLVM не предоставляет оператора‹‹ для потоковой передачи объектов StringRef в стандартные потоки (они используют пользовательский облегченный класс потока), я написал его.

Однако, когда я использую трейт, он не работает, если мой пользовательский оператор‹‹ объявлен после трейта (это происходит потому, что у меня есть трейт в одном заголовке, а функция оператора — в другом). ). Раньше я думал, что поиск в экземплярах шаблонов работает с точки зрения точки создания экземпляров, поэтому я думал, что он должен работать. На самом деле, как вы можете видеть ниже, с другим классом и его пользовательским оператором‹‹, объявленным после типажа, все работает как положено (поэтому я обнаружил эту проблему только сейчас), поэтому я не могу понять, что делает 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 перед объявлением типажа, оно печатает true. Итак, почему происходит эта странная вещь и как я могу решить эту проблему?


person gigabytes    schedule 27.04.2014    source источник
comment
Поместите свой оператор в то же пространство имен, что и тип, чтобы включить ADL.   -  person Yakk - Adam Nevraumont    schedule 27.04.2014
comment
@Yakk Это ответ, так почему бы не написать его?   -  person jrok    schedule 27.04.2014
comment
@jrok, потому что я укладываю ребенка спать, и это не способствует проверке того, что это настоящая проблема, разработка и т. Д. :)   -  person Yakk - Adam Nevraumont    schedule 27.04.2014
comment
Да, конечно АДЛ! Я должен был подумать об этом .. Спасибо! Однако ответ, в котором упоминаются точные правила поиска в экземплярах шаблонов, приветствуется :)   -  person gigabytes    schedule 27.04.2014


Ответы (1)


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

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

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

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

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

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

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

Обратите внимание, что директивы 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;
}
person Matthieu M.    schedule 10.05.2014