为什么我的特征模板类查找运算符<< 对于 llvm::StringRef?
Why can't my trait template class lookup operator<< for llvm::StringRef?
下面的问题是如何检测类型是否可以流式传输到std::ostream?我写了一个特性类,它说明某个类型是否可以流式传输到IO流。在我发现问题之前,这种特质似乎一直很有效。
我在一个使用LLVM的项目中使用代码,并使用他们的StringRef类(在精神上类似于所提出的std::string_view)。这里有一个指向该类的Doxygen文档的链接,如果需要,您可以从中找到它的声明头文件。由于LLVM不提供运算符<lt;为了将StringRef对象流式传输到std流(它们使用自定义的轻量级流类),我编写了一个。
然而,当我使用特性时,如果我的自定义运算符<lt;在特征之后声明为(发生这种情况是因为我在一个标头中有特征,在另一个头中有运算符<<函数)。我曾经认为,从实例化点的角度来看,模板实例化中的查找是有效的,所以我认为它应该有效。实际上,正如您在下面看到的,使用另一个类及其自定义运算符<lt;,在特性之后声明,一切都按预期工作(这就是为什么我现在才发现这个问题),所以我不知道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 << "LoLn";
}
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;对于特征声明之前的StringRef,它将打印true。那么,为什么会发生这种奇怪的事情,我该如何解决这个问题呢?
正如Yakk所提到的,这只是ADL:参数相关查找。
如果您不想麻烦,请记住,您应该始终在与其至少一个参数相同的命名空间中编写一个自由函数。在您的情况下,由于禁止向std
添加函数,这意味着将您的函数添加到llvm
命名空间中。事实上,您需要用llvm::
来限定StringRef
参数,这是一个彻头彻尾的错误。
函数解析的规则相当复杂,但作为一个快速的草图:
- 名称查找:收集一组潜在的候选者
- 过载解决方案:从潜力中选择最佳候选人
- 专业化解决方案:如果候选是一个函数模板,请检查是否有任何可能应用的专业化
我们在这里关注的名称查找阶段相对简单。简而言之:
- 它扫描参数的名称空间,然后扫描它们的父名称空间。。。直到它达到全球范围
- 然后继续扫描当前作用域,然后扫描其父作用域。。。直到它达到全球范围
可能是为了允许隐藏(就像任何其他名称查找一样),查找会在遇到匹配的第一个作用域停止,并傲慢地忽略周围的任何作用域。
注意,using
指令(例如using ::operator<<;
)可用于引入来自另一个作用域的名称。不过,这很麻烦,因为它把责任推给了客户,所以请不要把它的可用性作为草率的借口(我看到过这样做:x)。
阴影示例:打印"Hello, World"
时不会产生模糊性错误。
#include <iostream>
namespace hello { namespace world { struct A{}; } }
namespace hello { void print(world::A) { std::cout << "Hellon"; } }
namespace hello { namespace world { void print(A) { std::cout << "Hello, Worldn"; } } }
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;
}
- 请解释这句话(cout<<1+int((a<b)^((b-a)&1) )<<endl
- 呼叫运营商<<临时
- 如何防止clang格式在流运算符调用之间添加换行符<<
- <<操作员在下面的行中工作
- 如何显式调用运算符<<
- 模板操作员&lt;未打电话
- C / CUDA中的模板方法是3个角括号(&lt;&lt;&lt;)
- C - 创建矢量&lt; vector&lt; double&gt;&gt;矩阵具有分配而不是inizializ
- 错误:调用"std::vector<:vector<int>>::p ush_back(std::vector<std::__cxx11::basic_string<
- C 建造者Clang STD :: Sill,找不到超载的操作员&lt;
- 为什么STD :: MAP需要操作员&lt;以及我如何写一个
- 为什么“操作员”需要const但不是为“运营商&lt;”
- 为什么将此对向量&lt; map&lt; int,int&gt;&gt;中的地图进行更新.失败
- C :对矢量进行排序&lt; struct&gt;(结构有2个整数)基于结构的整数之一
- 明确的专业化“ CheckIntmap&lt;&gt;”实例化
- 什么是模板&lt;&gt;inline bla bla
- 左角支架解释为操作员&lt;而不是模板参数
- 编辑C Qlist&lt; object*&gt; gt;QML代码和一些QML警告中的模型
- 超载操作员&lt;&lt; - 必须是二进制操作员
- 没有匹配的“运营商&lt;&lt;”