为什么我的trait模板类查找运算符<<对于llvm :: StringRef?

时间:2014-04-27 16:19:22

标签: c++ c++11 llvm typetraits llvm-c++-api

在问题How can I detect if a type can be streamed to an std::ostream?之后我写了一个特征类,说明是否可以将某些类型流式传输到IO流。直到现在我发现了一个问题,这个特性似乎运作良好。

我在使用LLVM的项目中使用代码,我正在使用他们的StringRef类(它与提议的std :: string_view类似)。 Here是该类的Doxygen doc的链接,如果需要,您可以从中找到它的声明头文件。由于LLVM不提供运算符<<将StringRef对象流式传输到std流(它们使用自定义的轻量级流类),我写了一个。

然而,当我使用特征时,如果我的自定义运算符<<在特征之后声明(这是因为我在一个标题中有特征而在另一个标题中有操作符<< function)。我曾经认为模板实例化中的查找是从实例化点的角度来看的,所以我认为它应该可行。实际上,正如你在下面看到的那样,使用另一个类及其自定义运算符<&lt ;,在特征之后声明,一切都按预期工作(这就是为什么我现在才发现这个问题),所以我无法弄清楚是什么使得StringRef special。

这是完整的例子:

#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

如果我移动运算符的声明&lt;&lt;对于StringRef 之前特征声明,它会输出true。 那么为什么会发生这种奇怪的事情呢?我该如何解决这个问题?

1 个答案:

答案 0 :(得分:1)

如Yakk所述,这只是ADL:Argument Dependent Lookup。

如果您不想打扰,请记住您应该始终在与其至少一个参数相同的命名空间中编写一个自由函数。在您的情况下,由于禁止向std添加函数,这意味着将您的函数添加到llvm命名空间中。您需要使用StringRef限定llvm::参数的事实是一个死的赠品。

功能解析的规则相当复杂,但作为快速草图:

  • 名称查询:收集一组潜在候选人
  • 重载决议:选择潜力中的最佳候选者
  • 专业化解决方案:如果候选人是功能模板,请检查可以应用的任何专业化

我们关注的名称查找阶段相对简单。简而言之:

  • 它会扫描参数的名称空间,然后是父母,......直到它到达全局范围
  • 然后通过扫描当前范围,然后扫描其父范围,......直到它到达全局范围

可能to allow shadowing(与任何其他名称查找一样),查找在遇到匹配的第一个范围停止,并且傲慢地忽略任何周围的范围。

请注意,using指令(例如using ::operator<<;)可用于从另一个范围引入名称。虽然它很麻烦,因为它把责任放在了客户身上,所以请不要依赖它作为邋iness的借口(我已经看过了:x)。


shadowing的示例:这会打印"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;
}

interrupted search的示例:::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;
}