重载命名空间中类模板的输出运算符

发布于 2024-08-19 05:21:03 字数 1371 浏览 8 评论 0 原文

我这个程序

#include <iostream>
#include <sstream>
#include <iterator>
#include <vector>
#include <algorithm>
using namespace std ;

#if 0
namespace skg 
{
 template <class T>
  struct Triplet ;
}

template <class T>
ostream& operator<< (ostream& os, const skg::Triplet<T>& p_t) ;
#endif

namespace skg
{
 template <class T>
  struct Triplet
  {
 //  friend ostream& ::operator<< <> (ostream& os, const Triplet<T>& p_t) ;

   private:
   T x, y, z ;

   public:
   Triplet (const T& p_x, const T& p_y, const T& p_z)
    : x(p_x), y(p_y), z(p_z) { }
  } ;
}

template <class T>
ostream& operator<< (ostream& os, const skg::Triplet<T>& p_t)
{
 os << '(' << p_t.x << ',' << p_t.y << ',' << p_t.z << ')' ;
 return os ;
}

namespace {
 void printVector()
 {
  typedef skg::Triplet<int> IntTriplet ;

  vector< IntTriplet > vti ;
  vti.push_back (IntTriplet (1, 2, 3)) ;
  vti.push_back (IntTriplet (5, 5, 66)) ;

  copy (vti.begin(), vti.end(), ostream_iterator<IntTriplet> (cout, "\n")) ;
 }
}
int main (void)
{
 printVector() ;
}

编译失败,因为编译器找不到 skg::Triplet 的任何输出运算符。但输出运算符确实存在。

如果我将 Triplet 从 skg 命名空间移动到全局命名空间,一切都会正常。这里出了什么问题?

I've this program

#include <iostream>
#include <sstream>
#include <iterator>
#include <vector>
#include <algorithm>
using namespace std ;

#if 0
namespace skg 
{
 template <class T>
  struct Triplet ;
}

template <class T>
ostream& operator<< (ostream& os, const skg::Triplet<T>& p_t) ;
#endif

namespace skg
{
 template <class T>
  struct Triplet
  {
 //  friend ostream& ::operator<< <> (ostream& os, const Triplet<T>& p_t) ;

   private:
   T x, y, z ;

   public:
   Triplet (const T& p_x, const T& p_y, const T& p_z)
    : x(p_x), y(p_y), z(p_z) { }
  } ;
}

template <class T>
ostream& operator<< (ostream& os, const skg::Triplet<T>& p_t)
{
 os << '(' << p_t.x << ',' << p_t.y << ',' << p_t.z << ')' ;
 return os ;
}

namespace {
 void printVector()
 {
  typedef skg::Triplet<int> IntTriplet ;

  vector< IntTriplet > vti ;
  vti.push_back (IntTriplet (1, 2, 3)) ;
  vti.push_back (IntTriplet (5, 5, 66)) ;

  copy (vti.begin(), vti.end(), ostream_iterator<IntTriplet> (cout, "\n")) ;
 }
}
int main (void)
{
 printVector() ;
}

Compilation fails because compiler could not find any output operator for skg::Triplet. But output operator does exist.

If I move Triplet from skg namespace to global namespace everything works fine. what is wrong here ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

栀梦 2024-08-26 05:21:03

您需要将 operator<< 的实现移至与您的类相同的命名空间中。它正在寻找:

ostream& operator<< (ostream& os, const skg::Triplet<T>& p_t)

但由于参数依赖查找(ADL)的缺陷而找不到它。 ADL 意味着当您调用自由函数时,它会在其参数的命名空间中查找该函数。这与我们可以这样做的原因相同:

std::cout << "Hello" << std::endl;

即使 operator<<(std::ostream&, const char*) 位于 std 命名空间中。对于您的调用,这些命名空间是 stdskg

它将在两者中查找,而不是在 skg 中找到一个(因为您的位于全局范围内),然后在 std 中查找。它将看到可能性(所有正常的运算符<<),但没有一个匹配。 由于运行的代码(ostream_iterator 中的代码)位于命名空间 std 中,因此对全局命名空间的访问完全消失了。

通过放置运算符在同一个命名空间中,ADL 可以工作。 Herb Sutter 在一篇文章中对此进行了讨论:“一个温和的建议:修复 ADL。”。 (PDF)。事实上,这是文章中的一个片段(展示了一个缺点):

// Example 2.4
//
// In some library header:
//
namespace N { class C {}; }
int operator+( int i, N::C ) { return i+1; }

// A mainline to exercise it:
//
#include <numeric>
int main() {
    N::C a[10];
    std::accumulate( a, a+10, 0 ); // legal? not specified by the standard
}

与您的情况相同。

Sutter 和 & 出版的《C++ 编码标准》一书Alexandrescu 有一个有用的指南:

  • 将类型及其非成员函数接口保留在同一命名空间中。
  • 遵循它,你和 ADL 都会很高兴。我推荐这本书,即使你买不到,至少也可以阅读我上面链接的 PDF;它包含您应该需要的相关信息。


    请注意,移动运算符后,您将需要您的friend指令(以便您可以访问私有变量):

    template <typename U>
    friend ostream& operator<< (ostream& os, const Triplet<U>& p_t);
    

    还有ta-da!固定的。

    You need to move your implementation of operator<< into the same namespace as your class. It's looking for:

    ostream& operator<< (ostream& os, const skg::Triplet<T>& p_t)
    

    But won't find it because of a short-coming in argument-dependent look-up (ADL). ADL means that when you call a free function, it'll look for that function in the namespaces of it's arguments. This is the same reason we can do:

    std::cout << "Hello" << std::endl;
    

    Even though operator<<(std::ostream&, const char*) is in the std namespace. For your call, those namespaces are std and skg.

    It's going to look in both, not find one in skg (since yours is in the global scope), then look in std. It will see possibilities (all the normal operator<<'s), but none of those match. Because the code running (the code in ostream_iterator) is in the namespace std, access to the global namespace is completely gone.

    By placing your operator in the same namespace, ADL works. This is discussed in an article by Herb Sutter: "A Modest Proposal: Fixing ADL.". (PDF). In fact, here's a snippet from the article (demonstrating a shortcoming):

    // Example 2.4
    //
    // In some library header:
    //
    namespace N { class C {}; }
    int operator+( int i, N::C ) { return i+1; }
    
    // A mainline to exercise it:
    //
    #include <numeric>
    int main() {
        N::C a[10];
        std::accumulate( a, a+10, 0 ); // legal? not specified by the standard
    }
    

    Same situation you have.

    The book "C++ Coding Standards" by Sutter and & Alexandrescu has a useful guideline:

    1. Keep a type and its nonmember function interface in the same namespace.

    Follow it and you and ADL will be happy. I recommend this book, and even if you can't get one at least read the PDF I linked above; it contains the relevant information you should need.


    Note that after you move the operator, you'll need your friend directive (so you can access private variables):

    template <typename U>
    friend ostream& operator<< (ostream& os, const Triplet<U>& p_t);
    

    And ta-da! Fixed.

    ~没有更多了~
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文