测试是否存在左移运算符

Jef*_*ant 5 c++ boost stl type-traits

我正在尝试找到一个工作类型特征来检测给定类型是否具有左移位运算符重载std::ostream(例如,与std::cout或可互操作boost::lexical_cast).我已经取得了成功,boost::has_left_shift除了类型是POD或std::string类型的STL容器的情况.我怀疑这与STL类型或operator << functions的特化有关.使用有效左移运算符来一般识别类型的正确方法是什么std::ostream?如果这不可行,是否有一个单独的方法来检测POD或std :: string类型的STL容器上的左移位运算符的重载?

下面的代码显示了我当前正在使用的内容,并演示了即使在下一行调用它也boost::has_left_shift无法检测到重载operator<<函数.该程序在GCC 4.5.1或更高版本和clang 3.1中编译和工作.

为了避免明显的响应,我尝试用operator<<特定版本替换模板化函数,因为各种类型都没有用.我还尝试了两种类型的const-ness和l-value/r-values说明符的各种组合(各种调整引导我指向operator<<带有r值ostream 的重载的编译器消息).我也试过实现自己的特性,最多给我相同的结果boost::has_left_shift.

提前感谢您提供的任何帮助.如果能够详细解释为什么会出现这种行为以及解决方案如何工作,我也会非常感激.我正在扩展我的模板知识的极限,并且很想知道为什么这不起作用,正如我认为的那样.

#include <string>
#include <vector>
#include <iostream>
#include <boost/lexical_cast.hpp>
#include <boost/type_traits/has_left_shift.hpp>

using namespace std;

struct Point {
    int x;
    int y;
    Point(int x, int y) : x(x), y(y) {}
    string getStr() const { return "("+boost::lexical_cast<string>(x)+","+boost::lexical_cast<string>(y)+")"; }
};

ostream& operator<<(ostream& stream, const Point& p)
{
    stream << p.getStr();
    return stream;
}

template <typename T>
ostream& operator<<(ostream& stream, const std::vector<T>& v)
{
    stream << "[";
    for(auto it = v.begin(); it != v.end(); ++it)
    {
        if(it != v.begin())
            stream << ", ";
        stream << *it;
    }
    stream << "]";
    return stream;
}

template <typename T>
void print(const string& name, T& t)
{
    cout << name << " has left shift = " << boost::has_left_shift<ostream , T>::value << endl;
    cout << "t = " << t << endl << endl;
}

int main()
{
    cout << boolalpha;

    int i = 1;
    print("int", i);

    string s = "asdf";
    print("std::string", s);

    Point p(2,3);
    print("Point", p);

    vector<int> vi({1, 2, 3});
    print("std::vector<int>", vi);

    vector<string> vs({"x", "y", "z"});
    print("std::vector<std::string>", vs);

    vector<Point> vp({Point(1,2), Point(3,4), Point(5,6)});
    print("std::vector<Point>", vp);
}
Run Code Online (Sandbox Code Playgroud)

And*_*owl 6

它不起作用的原因是C++有时会出现令人惊讶(但很有动力)的解析函数调用的规则.特别是,名称查找首先在调用发生的命名空间和参数的命名空间(对于UDT)中执行:如果找到具有匹配名称的函数(或者如果匹配的内置运算符),则选择它(或者如果找到多个,则执行重载决策).

只有在参数的命名空间中找不到具有匹配名称的函数时,才会检查父命名空间.如果找到具有匹配名称的函数但是对于解析调用是不可行的,或者如果调用不明确,则编译器将不会继续查找父命名空间,希望找到更好或明确的匹配:相反,它将得出结论,没有办法解决这个电话.

这个机制在Stephan T. Lavavej的演讲中以及Herb Sutter的这篇旧文章中得到了很好的解释.

在您的情况下,检查此运算符是否存在的函数位于boost命名空间中.你的论点是无论是从std命名空间(ostream,string,vector),或者是荚(int).在std命名空间中,存在一个不可行的重载operator <<,因此编译器不会在定义重载的父(全局)命名空间中查找.它将简单地断定在boost命名空间中完成的(模拟)调用以检查是否operator <<已定义无法解析.

现在boost::has_left_shift可能有一些SFINAE机制用于将原本可能是编译错误的转换为失败的替换,并将分配false给value静态变量.

更新:

答案的原始部分解释了为什么这不起作用.现在让我们看看是否有办法解决它.由于使用了ADL并且std命名空间包含一个非可行的重载operator <<,事实上阻止了解析调用的尝试,因此可能会试图将全局命名空间的可行重载移动operator <<到std命名空间中.

唉,C++标准禁止扩展std命名空间(这是我们在添加新的重载时要做的事情operator <<).允许的是专门化命名空间中定义的模板函数(除非另有说明); 但是,这对我们没有帮助,因为没有模板的参数可以专门化.此外,功能模板不能部分专业化,这会使事情变得更加笨拙.stdvector<int>

但最后一种可能性是:将重载添加到发生调用解析的命名空间.这是Boost.TypeTraits机器内的某个地方.特别是,我们感兴趣的是调用的命名空间的名称.

在当前版本的库中,恰好是boost::detail::has_left_shift_impl,但我不确定这在不同的Boost版本中是多么便携.

但是,如果您确实需要一种解决方法,则可以在该命名空间中声明您的运算符:

namespace boost 
{ 
    namespace detail 
    { 
        namespace has_left_shift_impl
        {
            ostream& operator<<(ostream& stream, const Point& p)
            {
                stream << p.getStr();
                return stream;
            }

            template <typename T>
            std::ostream& operator<<(std::ostream& stream, const std::vector<T>& v)
            {
                stream << "[";
                for(auto it = v.begin(); it != v.end(); ++it)
                {
                    if(it != v.begin())
                        stream << ", ";
                    stream << *it;
                }
                stream << "]";
                return stream;
            }
        } 
    } 
}
Run Code Online (Sandbox Code Playgroud)

事情将开始奏效.

但有一点需要注意:虽然这可以用GCC 4.7.2和预期的输出进行编译和运行.然而,锵3.2似乎需要在要定义的过载boost::details::has_left_shift_impl 之前的has_left_shift.hpp包括报头.我相信这是一个错误.