1

在处理我的基本向量库时,我一直在尝试使用一种很好的语法来进行基于 swizzle 的打印。尝试打印与所讨论的向量不同维度的调酒时会出现问题。在 GCC 4.0 中,我最初为每个向量中的每个维度使用了朋友 << 重载函数(带有一个主体,即使它重复了代码),这导致代码工作,即使实际上从未调用过非本机维度代码. 这在 GCC 4.2 中失败了。我最近意识到(愚蠢的我)只需要函数声明,而不是代码主体,所以我这样做了。现在我在 GCC 4.0 和 4.2 上都得到了同样的警告:

LINE 50 warning: friend declaration 'std::ostream& operator<<(std::ostream&, const VECTOR3<TYPE>&)' declares a non-template function

加上其他函数声明的五个相同警告。

下面的示例代码准确地展示了正在发生的事情,并包含重现问题所需的所有代码。

#include <iostream> // cout, endl
#include <sstream> // ostream, ostringstream, string

using std::cout;
using std::endl;
using std::string;
using std::ostream;

// Predefines
template <typename TYPE> union VECTOR2;
template <typename TYPE> union VECTOR3;
template <typename TYPE> union VECTOR4;

typedef VECTOR2<float> vec2;
typedef VECTOR3<float> vec3;
typedef VECTOR4<float> vec4;

template <typename TYPE>
union VECTOR2
{
private:
    struct { TYPE x, y; } v;

    struct s1 { protected: TYPE x, y; };
    struct s2 { protected: TYPE x, y; };
    struct s3 { protected: TYPE x, y; };
    struct s4 { protected: TYPE x, y; };

    struct X : s1 { operator TYPE() const { return s1::x; } };
    struct XX : s2 { operator VECTOR2<TYPE>() const { return VECTOR2<TYPE>(s2::x, s2::x); } };
    struct XXX : s3 { operator VECTOR3<TYPE>() const { return VECTOR3<TYPE>(s3::x, s3::x, s3::x); } };
    struct XXXX : s4 { operator VECTOR4<TYPE>() const { return VECTOR4<TYPE>(s4::x, s4::x, s4::x, s4::x); } };
public:
    VECTOR2() {}
    VECTOR2(const TYPE& x, const TYPE& y) { v.x = x; v.y = y; }

    X x;
    XX xx;
    XXX xxx;
    XXXX xxxx;

    // Overload for cout
    friend ostream& operator<<(ostream& os, const VECTOR2<TYPE>& toString)
    {
        os << "(" << toString.v.x << ", " << toString.v.y << ")";
        return os;
    }
    friend ostream& operator<<(ostream& os, const VECTOR3<TYPE>& toString);
    friend ostream& operator<<(ostream& os, const VECTOR4<TYPE>& toString);
};

template <typename TYPE>
union VECTOR3
{
private:
    struct { TYPE x, y, z; } v;

    struct s1 { protected: TYPE x, y, z; };
    struct s2 { protected: TYPE x, y, z; };
    struct s3 { protected: TYPE x, y, z; };
    struct s4 { protected: TYPE x, y, z; };

    struct X : s1 { operator TYPE() const { return s1::x; } };
    struct XX : s2 { operator VECTOR2<TYPE>() const { return VECTOR2<TYPE>(s2::x, s2::x); } };
    struct XXX : s3 { operator VECTOR3<TYPE>() const { return VECTOR3<TYPE>(s3::x, s3::x, s3::x); } };
    struct XXXX : s4 { operator VECTOR4<TYPE>() const { return VECTOR4<TYPE>(s4::x, s4::x, s4::x, s4::x); } };
public:
    VECTOR3() {}
    VECTOR3(const TYPE& x, const TYPE& y, const TYPE& z) { v.x = x; v.y = y; v.z = z; }

    X x;
    XX xx;
    XXX xxx;
    XXXX xxxx;

    // Overload for cout
    friend ostream& operator<<(ostream& os, const VECTOR3<TYPE>& toString)
    {
        os << "(" << toString.v.x << ", " << toString.v.y << ", " << toString.v.z << ")";
        return os;
    }
    friend ostream& operator<<(ostream& os, const VECTOR2<TYPE>& toString);
    friend ostream& operator<<(ostream& os, const VECTOR4<TYPE>& toString);
};

template <typename TYPE>
union VECTOR4
{
private:
    struct { TYPE x, y, z, w; } v;

    struct s1 { protected: TYPE x, y, z, w; };
    struct s2 { protected: TYPE x, y, z, w; };
    struct s3 { protected: TYPE x, y, z, w; };
    struct s4 { protected: TYPE x, y, z, w; };

    struct X : s1 { operator TYPE() const { return s1::x; } };
    struct XX : s2 { operator VECTOR2<TYPE>() const { return VECTOR2<TYPE>(s2::x, s2::x); } };
    struct XXX : s3 { operator VECTOR3<TYPE>() const { return VECTOR3<TYPE>(s3::x, s3::x, s3::x); } };
    struct XXXX : s4 { operator VECTOR4<TYPE>() const { return VECTOR4<TYPE>(s4::x, s4::x, s4::x, s4::x); } };
public:
    VECTOR4() {}
    VECTOR4(const TYPE& x, const TYPE& y, const TYPE& z, const TYPE& w) { v.x = x; v.y = y; v.z = z; v.w = w; }

    X x;
    XX xx;
    XXX xxx;
    XXXX xxxx;

    // Overload for cout
    friend ostream& operator<<(ostream& os, const VECTOR4& toString)
    {
        os << "(" << toString.v.x << ", " << toString.v.y << ", " << toString.v.z << ", " << toString.v.w << ")";
        return os;
    }
    friend ostream& operator<<(ostream& os, const VECTOR2<TYPE>& toString);
    friend ostream& operator<<(ostream& os, const VECTOR3<TYPE>& toString);
};

// Test code
int main (int argc, char * const argv[])
{
    vec2 my2dVector(1, 2);

    cout << my2dVector.x << endl;
    cout << my2dVector.xx << endl;
    cout << my2dVector.xxx << endl;
    cout << my2dVector.xxxx << endl;

    vec3 my3dVector(3, 4, 5);

    cout << my3dVector.x << endl;
    cout << my3dVector.xx << endl;
    cout << my3dVector.xxx << endl;
    cout << my3dVector.xxxx << endl;

    vec4 my4dVector(6, 7, 8, 9);

    cout << my4dVector.x << endl;
    cout << my4dVector.xx << endl;
    cout << my4dVector.xxx << endl;
    cout << my4dVector.xxxx << endl;

    return 0;
}

该代码工作并产生正确的输出,但我更喜欢尽可能无警告代码。我遵循了编译器给我的建议(在此处总结并由论坛和 StackOverflow 描述为该警告的答案),并添加了两件本应告诉编译器发生了什么的事情。也就是说,我在模板联合的预定义之后将函数定义添加为非朋友:

template <typename TYPE> ostream& operator<<(ostream& os, const VECTOR2<TYPE>& toString);
template <typename TYPE> ostream& operator<<(ostream& os, const VECTOR3<TYPE>& toString);
template <typename TYPE> ostream& operator<<(ostream& os, const VECTOR4<TYPE>& toString);

并且,对于导致问题的每个友元函数,我<>在函数名称后添加了一个,例如 VECTOR2 的情况:

friend ostream& operator<< <> (ostream& os, const VECTOR3<TYPE>& toString);
friend ostream& operator<< <> (ostream& os, const VECTOR4<TYPE>& toString);

但是,这样做会导致错误,例如:

LINE 139: error: no match for 'operator<<' in 'std::cout << my2dVector.VECTOR2<float>::xxx'

这是怎么回事?是与这些模板化的类联合类结构如何相互关联有关,还是由于联合本身?

更新

在重新思考所涉及的问题,并听取了 Potatoswatter 的各种建议后,我找到了最终的解决方案。与互联网上几乎每个 cout 重载示例不同,我不需要访问私有成员信息,但可以使用公共接口来做我想做的事。因此,我为调用真正的友元重载函数的调酒部分创建了一个非友元重载函数。这绕过了编译器对模板化友元函数的问题。我已添加到我的项目的最新版本。它现在适用于我尝试过的两个 GCC 版本,没有任何警告。有问题的代码如下所示:

template <typename SWIZZLE> inline
typename EnableIf< Is2D< typename SWIZZLE::PARENT >, ostream >::type&
operator<<(ostream& os, const SWIZZLE& printVector)
{
        os << (typename SWIZZLE::PARENT(printVector));
        return os;
}

template <typename SWIZZLE> inline
typename EnableIf< Is3D< typename SWIZZLE::PARENT >, ostream >::type&
operator<<(ostream& os, const SWIZZLE& printVector)
{
        os << (typename SWIZZLE::PARENT(printVector));
        return os;
}

template <typename SWIZZLE> inline
typename EnableIf< Is4D< typename SWIZZLE::PARENT >, ostream >::type&
operator<<(ostream& os, const SWIZZLE& printVector)
{
        os << (typename SWIZZLE::PARENT(printVector));
        return os;
}
4

2 回答 2

2

您不能friend仅在类模板中声明 free 。该定义在类外部不可见,直到它在全局范围内显式声明。(编辑:此代码是一个例外;这些朋友是main由 ADL 找到的。但是,我不明白为什么 ADL 在其他模板中找不到他们……)因此,例如,在 的右模板大括号之后VECTOR2,添加原型

template< class TYPE >
ostream& operator<<(ostream& os, const VECTOR2<TYPE>& toString);

除此之外,friend您目前拥有的作为裸原型的 s 对我来说看起来没有必要。而且s1看起来s4显然是多余的。整个事情的意图我无法辨别。

更正

在这种情况下,friend由于使用隐式 cast 的方式, free 不能是模板operator。所以上面的声明不会匹配模板中的非模板声明VECTOR。模板通过友元机制生成非模板函数是一种奇怪而棘手的语言功能,在这里可以正确使用,几乎没有回旋余地。最好的选择是禁用警告而不是尝试重构复杂的重载解决方案。

更新

实际上,这里有一个简单的重构。只需将operator<<隐式转换运算符添加到 swizzle 类中即可。(对不起,草率的格式。)

#include <iostream> // cout, endl
#include <sstream> // ostream, ostringstream, string

using std::cout;
using std::endl;
using std::string;
using std::ostream;

// Predefines
template <typename TYPE> union VECTOR2;
template <typename TYPE> union VECTOR3;
template <typename TYPE> union VECTOR4;

typedef VECTOR2<float> vec2;
typedef VECTOR3<float> vec3;
typedef VECTOR4<float> vec4;

template< class TYPE2 >
ostream& operator<<(ostream& os, const VECTOR2<TYPE2>& toString)
{
    os << "(" << toString.v.x << ", " << toString.v.y << ")";
    return os;
}

template <typename TYPE>
union VECTOR2
{
private:
    struct { TYPE x, y; } v;

    struct s1 { protected: TYPE x, y; };
    struct s2 { protected: TYPE x, y; };
    struct s3 { protected: TYPE x, y; };
    struct s4 { protected: TYPE x, y; };

    struct X : s1 { operator TYPE() const { return s1::x; } };
    struct XX : s2 { operator VECTOR2<TYPE>() const { return VECTOR2<TYPE>(s2::x, s2::x); }
    friend ostream& operator<<( ostream &os, XX const &v ) { os << VECTOR2<TYPE>( v ); } };
    struct XXX : s3 { operator VECTOR3<TYPE>() const { return VECTOR3<TYPE>(s3::x, s3::x, s3::x); }
    friend ostream& operator<<( ostream &os, XXX const &v ) { os << VECTOR3<TYPE>( v ); } };
    struct XXXX : s4 { operator VECTOR4<TYPE>() const { return VECTOR4<TYPE>(s4::x, s4::x, s4::x, s4::x); }
    friend ostream& operator<<( ostream &os, XXXX const &v ) { os << VECTOR4<TYPE>( v ); } };
public:
    VECTOR2() {}
    VECTOR2(const TYPE& x, const TYPE& y) { v.x = x; v.y = y; }

    X x;
    XX xx;
    XXX xxx;
    XXXX xxxx;

    // Overload for cout
    friend ostream& operator<< <>(ostream& os, const VECTOR2<TYPE>& toString);
};

template< class TYPE >
ostream& operator<<(ostream& os, const VECTOR3<TYPE>& toString)
{
    os << "(" << toString.v.x << ", " << toString.v.y << ", " << toString.v.z << ")";
    return os;
}

template <typename TYPE>
union VECTOR3
{
private:
    struct { TYPE x, y, z; } v;

    struct s1 { protected: TYPE x, y, z; };
    struct s2 { protected: TYPE x, y, z; };
    struct s3 { protected: TYPE x, y, z; };
    struct s4 { protected: TYPE x, y, z; };

    struct X : s1 { operator TYPE() const { return s1::x; } };
    struct XX : s2 { operator VECTOR2<TYPE>() const { return VECTOR2<TYPE>(s2::x, s2::x); }
    friend ostream& operator<<( ostream &os, XX const &v ) { os << VECTOR2<TYPE>( v ); } };
    struct XXX : s3 { operator VECTOR3<TYPE>() const { return VECTOR3<TYPE>(s3::x, s3::x, s3::x); }
    friend ostream& operator<<( ostream &os, XXX const &v ) { os << VECTOR3<TYPE>( v ); } };
    struct XXXX : s4 { operator VECTOR4<TYPE>() const { return VECTOR4<TYPE>(s4::x, s4::x, s4::x, s4::x); }
    friend ostream& operator<<( ostream &os, XXXX const &v ) { os << VECTOR4<TYPE>( v ); } };
public:
    VECTOR3() {}
    VECTOR3(const TYPE& x, const TYPE& y, const TYPE& z) { v.x = x; v.y = y; v.z = z; }

    X x;
    XX xx;
    XXX xxx;
    XXXX xxxx;

    // Overload for cout
    friend ostream& operator<< <>(ostream& os, const VECTOR3<TYPE>& toString);
};

template< class TYPE >
ostream& operator<<(ostream& os, const VECTOR4<TYPE>& toString)
{
    os << "(" << toString.v.x << ", " << toString.v.y << ", " << toString.v.z << ", " << toString.v.w << ")";
    return os;
}

template <typename TYPE>
union VECTOR4
{
private:
    struct { TYPE x, y, z, w; } v;

    struct s1 { protected: TYPE x, y, z, w; };
    struct s2 { protected: TYPE x, y, z, w; };
    struct s3 { protected: TYPE x, y, z, w; };
    struct s4 { protected: TYPE x, y, z, w; };

    struct X : s1 { operator TYPE() const { return s1::x; } };
    struct XX : s2 { operator VECTOR2<TYPE>() const { return VECTOR2<TYPE>(s2::x, s2::x); }
    friend ostream& operator<<( ostream &os, XX const &v ) { os << VECTOR2<TYPE>( v ); } };
    struct XXX : s3 { operator VECTOR3<TYPE>() const { return VECTOR3<TYPE>(s3::x, s3::x, s3::x); }
    friend ostream& operator<<( ostream &os, XXX const &v ) { os << VECTOR3<TYPE>( v ); } };
    struct XXXX : s4 { operator VECTOR4<TYPE>() const { return VECTOR4<TYPE>(s4::x, s4::x, s4::x, s4::x); }
    friend ostream& operator<<( ostream &os, XXXX const &v ) { os << VECTOR4<TYPE>( v ); } };
public:
    VECTOR4() {}
    VECTOR4(const TYPE& x, const TYPE& y, const TYPE& z, const TYPE& w) { v.x = x; v.y = y; v.z = z; v.w = w; }

    X x;
    XX xx;
    XXX xxx;
    XXXX xxxx;

    // Overload for cout
    friend ostream& operator<< <>(ostream& os, const VECTOR4& toString);
};

// Test code
int main (int argc, char * const argv[])
{
    vec2 my2dVector(1, 2);

    cout << my2dVector.x << endl;
    cout << my2dVector.xx << endl;
    cout << my2dVector.xxx << endl;
    cout << my2dVector.xxxx << endl;

    vec3 my3dVector(3, 4, 5);

    cout << my3dVector.x << endl;
    cout << my3dVector.xx << endl;
    cout << my3dVector.xxx << endl;
    cout << my3dVector.xxxx << endl;

    vec4 my4dVector(6, 7, 8, 9);

    cout << my4dVector.x << endl;
    cout << my4dVector.xx << endl;
    cout << my4dVector.xxx << endl;
    cout << my4dVector.xxxx << endl;

    return 0;
}

很奇怪,我添加的函数仍然是在模板中定义的非模板朋友,与我删除的函数非常相似。但无论出于何种原因,GCC 都不会抱怨它们。如果是这样,无论如何,您可以将它们制作为模板并将它们移到适当的命名空间范围之外。

顺便说一句,我个人并不喜欢这个代码而不是原始代码。如果我是你,我只会禁用警告。

于 2010-08-15T23:32:09.373 回答
1

此处的常见问题解答http://gcc.gnu.org/faq.html在“杂项”部分下提供了更多详细信息

于 2010-08-16T04:49:45.947 回答