Templated << friend not working when in interrelationship with other templated union types

Posted by Dwight on Stack Overflow See other posts from Stack Overflow or by Dwight
Published on 2010-08-15T22:55:53Z Indexed on 2012/10/08 15:38 UTC
Read the original article Hit count: 297

Filed under:
|
|
|
|

While working on my basic vector library, I've been trying to use a nice syntax for swizzle-based printing. The problem occurs when attempting to print a swizzle of a different dimension than the vector in question. In GCC 4.0, I originally had the friend << overloaded functions (with a body, even though it duplicated code) for every dimension in each vector, which caused the code to work, even if the non-native dimension code never actually was called. This failed in GCC 4.2. I recently realized (silly me) that only the function declaration was needed, not the body of the code, so I did that. Now I get the same warning on both GCC 4.0 and 4.2:

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

Plus the five identical warnings more for the other function declarations.

The below example code shows off exactly what's going on and has all code necessary to reproduce the problem.

#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;
}

The code WORKS and produces the correct output, but I prefer warning free code whenever possible. I followed the advice the compiler gave me (summarized here and described by forums and StackOverflow as the answer to this warning) and added the two things that supposedly tells the compiler what's going on. That is, I added the function definitions as non-friends after the predefinitions of the templated unions:

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);

And, to each friend function that causes the issue, I added the <> after the function name, such as for VECTOR2's case:

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

However, doing so leads to errors, such as:

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

What's going on? Is it something related to how these templated union class-like structures are interrelated, or is it due to the unions themselves?

Update

After rethinking the issues involved and listening to the various suggestions of Potatoswatter, I found the final solution. Unlike just about every single cout overload example on the internet, I don't need access to the private member information, but can use the public interface to do what I wish. So, I make a non-friend overload functions that are inline for the swizzle parts that call the real friend overload functions. This bypasses the issues the compiler has with templated friend functions. I've added to the latest version of my project. It now works on both versions of GCC I tried with no warnings. The code in question looks like this:

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;
}

© Stack Overflow or respective owner

Related posts about c++

Related posts about templates