【问题标题】:problems with overload resolution and operator<< for templated types - part 2模板类型的重载决议和 operator<< 的问题 - 第 2 部分
【发布时间】:2016-05-10 13:19:37
【问题描述】:

给定以下代码:

#include <string>
#include <type_traits>
#include <sstream>
#include <vector>
#include <iostream>
using namespace std;

namespace has_insertion_operator_impl {
    typedef char no;
    typedef char yes[2];

    struct any_t {
        template <typename T>
        any_t(T const&);
    };

    no operator<<(ostream const&, any_t const&);

    yes& test(ostream&);
    no   test(no);

    template <typename T>
    struct has_insertion_operator {
        static ostream& s;
        static T const&      t;
        static bool const    value = sizeof(test(s << t)) == sizeof(yes);
    };
}

template <typename T>
struct has_insertion_operator : has_insertion_operator_impl::has_insertion_operator<T> {};

template <class T>
typename enable_if<has_insertion_operator<T>::value, string>::type stringify(const T& in) {
    stringstream stream;
    stream << in;
    return stream.str();
}

template <class T>
typename enable_if< ! has_insertion_operator<T>::value, string>::type stringify(const T&) {
    return "{?}";
}

// ======= OVERLOADS PROVIDED BY THE USER =======

template<typename T, typename T2>
struct myType { T data; T2 op; };

template<typename T, typename T2>
ostream& operator<<(ostream& s, const myType<T, T2>&) { s << "myType"; return s; }

template<typename T>
ostream& operator<<(ostream& s, const vector<T>&) { s << "vector<T>"; return s; }

template<typename T, typename A>
ostream& operator<<(ostream& s, const vector<T, A>&) { s << "vector<T, A>"; return s; }

int main() {
    myType<int, float> a;   cout << stringify(a) << endl; // prints "myType"
                            cout << stringify(6) << endl; // prints "6"
    vector<int> v(5);       cout << stringify(v) << endl; // prints "{?}"

    return 0;
}

为什么模板 myType&lt;&gt; 被字符串化,而模板化的 vector&lt;&gt; 类型却没有?

对于vector&lt;&gt; 类型,我得到默认的{?} 字符串化,但我显然希望调用底部的重载之一——就像myType&lt;&gt; 一样

编辑:

这里的实际问题是为什么has_insertion_operator&lt;vector&lt;int&gt;&gt; 是假的?

我在 C++98 中也需要这个

operator&lt;&lt; 重载应该在 stringify() 之后提供 - 就像 myType&lt;&gt; 一样

【问题讨论】:

  • 很明显为什么向量没有被字符串化。问题可以简化为“为什么has_insertion_operator&lt;vector&lt;int&gt;&gt; 是假的?”
  • @user2079303:它有什么“明显”的地方?那里有一个ostream&amp; operator&lt;&lt;(ostream&amp; s, const vector&lt;T&gt;&amp;)
  • @LightnessRacesinOrbit 实际上他是对的——这是特性的错——它驱动了enable_if&lt;&gt; 和 SFINAE
  • @onqtam:没有什么“显而易见”的。我认为 user2079303 没有意识到 OP 已经尝试创建自己的 operator&lt;&lt; 来匹配。
  • @LightnessRacesinOrbit 显然没有使用重载,因为它被enable_if&lt;has_insertion_operator&lt;T&gt;::value, string&gt;::type 禁用。不明显的是为什么 has_insertion_operator&lt;T&gt;::value 是假的,尽管ostream&amp; operator&lt;&lt;(ostream&amp; s, const vector&lt;T&gt;&amp;)。因此,我建议简化问题。

标签: c++ templates operator-overloading overload-resolution


【解决方案1】:

我认为问题在于查找,我会提供我的理解。

当您在 has... 结构参数依赖查找中调用 operator&lt;&lt; 时,由于 myType 与重载的 operator&lt;&lt; 位于相同的命名空间中,它会被创建,并且您会得到一个正确的字符串。但是,当您尝试输出vector 时,它会尝试通过相同的依赖于参数的查找规则搜索重载的operator&lt;&lt;,但由于std 命名空间中没有重载运算符,因此无法执行此操作。因此它会退回到从进行调用的命名空间开始的不合格搜索,因此它会找到您的存根 operator&lt;&lt;

因此,为了修复它,您可能已将 operator&lt;&lt; 重载放置到 std 命名空间(这是标​​准禁止的)或删除您的命名空间 - 它会产生相同的效果。

不过,您不必将所有内容都放在私有命名空间之外。在全局命名空间中做这样的事情就足够了:

typedef char no;
typedef char yes[2];
template<typename T>
no operator<<(ostream const&, T const&);

或者,如果可能的话,最好要求库用户将他们的重载放到他们的类所在的同一个命名空间中。不过,它不适用于std 成员。

【讨论】:

    【解决方案2】:

    这里有两个问题。

    首先是has_insertion_operator&lt;&gt; 谓词中有问题。

    我把它换成了这个……

    template<class T>
    struct has_insertion_operator
    {
        template<class U>
        static auto test(U*p) -> decltype((std::declval<std::ostream>() << (*p)), void(), std::true_type());
    
        template<class U>
        static auto test(...) -> std::false_type;
    
        static constexpr bool value = decltype(test<T>(0))::value;
    };
    

    ...解决了这个问题,并突出显示了下一个(这对你来说可能更严重):

    ./stringify.cpp:73:12: error: call to function 'operator<<' that is neither visible in the template definition nor found by argument-dependent lookup
        stream << in;
               ^
    ./stringify.cpp:100:37: note: in instantiation of function template specialization 'stringify<std::__1::vector<int, std::__1::allocator<int> > >' requested here
        vector<int> v(5);       cout << stringify(v) << endl; // prints "{?}"
                                        ^
    ./stringify.cpp:91:10: note: 'operator<<' should be declared prior to the call site
    ostream& operator<<(ostream& s, const vector<T>&) { s << "vector<T>"; return s; }
             ^
    1 error generated.
    

    这是因为模板函数operator&lt;&lt; &lt;std::vector...&gt;在定义之前就被引用了。

    一旦你将它的定义移到 stringify 的定义之上,一切正常。

    最后,注意:

    std::vector 重载operator&lt;&lt; 是一个非常糟糕的主意。以后会让人头疼的。

    但我使用的是 c++98,我希望用户能够提供自己的重载 [专业]

    好吧,让我们以简单(且更正确)的方式来做,它适用于所有类型的 c++,并且不会因为非法重载入侵 std 命名空间而引起头痛:

    #include <string>
    #include <sstream>
    #include <vector>
    #include <iostream>
    
    
    // define a template class emitter which by default simply calls operator<<
    template<class T>
    struct emitter
    {
        emitter(const T& v) : _v(v) {}
        std::ostream& operator()(std::ostream& os) const {
            return os << _v;
        }
        const T& _v;
    };
    
    // emitter<>'s are streamable
    template<class T>
    std::ostream& operator<<(std::ostream& os, const emitter<T>& e)
    {
        return e(os);
    }
    
    // a factory function to generate the correct emitter
    template<class T>
    emitter<T> emit(const T& v)
    {
        return emitter<T>(v);
    }
    
    // write one version of stringify in terms of emit<>()
    template <class T>
    std::string stringify(const T& in) {
        std::stringstream stream;
        stream << emit(in);
        return stream.str();
    }
    // ======= OVERLOADS PROVIDED BY THE USER =======
    
    template<typename T, typename T2>
    struct myType { T data; T2 op; };
    
    // user must provide an emitter for each custom type
    template<typename T, typename T2>
    struct emitter<myType<T, T2> >
    {
        typedef myType<T, T2> value_type;
        emitter(const value_type& v) : _v(v) {}
    
        std::ostream& operator()(std::ostream& os) const
        {
            return os << "myType";
        }
    private:
        const value_type& _v;
    };
    
    // and for any std:: templates he wants to support
    template<class V, class A>
    struct emitter<std::vector<V, A> >
    {
        typedef std::vector<V, A> value_type;
        emitter(const value_type& v) : _v(v) {}
    
        std::ostream& operator()(std::ostream& os) const
        {
            return os << "vector<T, A>";
        }
    private:
        const value_type& _v;
    };
    
    // test
    int main() {
        myType<int, float> a;  std::cout << stringify(a) << std::endl; // prints "myType"
                               std::cout << stringify(6) << std::endl; // prints "6"
        std::vector<int> v(5); std::cout << stringify(v) << std::endl; // prints "vector<T, A>"
    
        return 0;
    }
    

    【讨论】:

    • 我在 C++98 中需要这个 - 而且我需要重载低于 stringify() - 就像它目前与 myType&lt;&gt; 一样工作
    • @onqtam 在第一项上,我想一些调试会让你到达那里。第二个,我帮不了你。模板函数不能调用模板的通用形式尚不存在的模板函数。这是语言限制。
    • "模板函数不能调用模板的一般形式尚不存在的模板函数" 但 OP 询问 vectormyType 之间的差异
    • @PiotrSkotnicki 我不确定我是否关注。
    • quote: "对于 vector&lt;&gt; 类型,我得到默认的 {?} 字符串化,但我显然希望调用底部的重载之一 - 就像使用 myType&lt;&gt;",即编译器为myType找到operator&lt;&lt;,但它没有为vector找到vector
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多