views:

71

answers:

2

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;
}
+2  A: 

You can't declare a free friend solely inside a class template. The definition is not visible outside the class until it's explicitly declared at global scope. (Edit: this code is an exception; the friends are found in main by ADL. However, I don't understand why they aren't found by ADL in the other templates…) So, for example, after the closing template brace of VECTOR2, add the prototype

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

Aside from that, the friends you presently have as bare prototypes look unnecessary to me. And s1 to s4 seem obviously redundant. The intent of the whole thing I can't discern.

CORRECTION

In this case, the free friend cannot be a template because of the way implicit cast operators are used. So the above declaration won't match the non-template declaration within the VECTOR template. Generation of non-template functions by a template via the friend mechanism is a strange and tricky language feature, which is being used correctly here with very little wiggle room. The best option is to disable the warning rather than attempt to refactor the sophisticated overload resolution.

UPDATE

Actually, there is a simple refactoring here. Just add operator<< to the swizzle classes along with the implicit cast operator. (Sorry for the sloppy formatting.)

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

Quite strangely, the functions I added are still non-template friends defined within a template, quite similar all-around to the functions I eliminated. But GCC doesn't complain about them for whatever reason. If it did, you could make them templates and move them outside to proper namespace scope, anyway.

By the way, I don't personally prefer this code over the original. If I were you, I'd just disable the warning.

Potatoswatter
Tried it, couldn't make it work. Could you be more specific?The friend prototypes are there to prevent the compiler from complaining that there is no overload within vector2 that allows you to print vector3 or vector4s. However, those actual function bodies are already written in their respective vector dimensions. Vector2 knows how to print 2D vectors, vec3 prints 3d vectors, and so on. The s1 - s4 notation is useless here, but is a holdover from the complete code that saves a lot of redundant typing. The intent is fully explained here: http://code.google.com/p/svml/
Dwight
@Dwight: Sorry, now I've taken a closer look and I see what you're doing and why. I don't believe you can eliminate the warning. It is simply saying you are using a very esoteric language feature, but because of the way you need the implicit cast `operator` to be defined and found, there is no other way to do it. As GCC suggests, pass `-Wno-non-template-friend` to silence the warning.
Potatoswatter
I was afraid of that. Thank you for your assurance that I'm not crazy. :D Of course, I could simply use a function inside the main code every time I wished to print a swizzle vector: ToString(theSwizzle.xxxx). But I held out hope I could make the syntax a little nicer.
Dwight
@Dwight: Any free function will see the same problem as `operator<<`. `ToString` would still need to access `private: v` and moreover you would still need `xxxx` to implicitly cast itself back to `VECTOR` before `ToString` is even found by overload resolution. So the stream insertion syntax is the only easy, free part of this package. One possible simplification would be `public: v`; maybe you could hide the implementation using an anonymous namespace or something.
Potatoswatter
I believe I am handling that in the main code with SFINAE plus inner casts to regular vectors, then I just use the public-access single dimensional swizzles to access data. I am interested to see how the anonymous namespace would work.
Dwight
@Dwight: Actually since the only reason `operator<<` needs to be a free function is to let the implicit cast work, you can implement it as a template if you eliminate the cast, by implementing `operator<<` inside `X`, `XX`, etc. See update.
Potatoswatter
@PotatoswatterThe problem with this is when you add y, z, and w components (and all their possible variations). Adding just one more function to every "swizzle" as they are called would add at least a thousand more lines of code (to a file that only has 3000 lines as it is).
Dwight
@Dwight: Yes, but they all have extremely similar text. Given the repetition in the code before my change, macros would already be a good idea.
Potatoswatter
@Potatoswatter ARGH! Macros?! They're (mostly) evil! And I'm not sure how much I could do with macros either. Then again, after looking at your solution, I realized a way to make it work. I will post the solution in the update of my original post.
Dwight
@Dwight: Very nice!
Potatoswatter
+1  A: 

The faq here http://gcc.gnu.org/faq.html gives more detail under the section "Miscellaneous"

Chubsdad
Note that the desired behavior here is what the FAQ considers tedious and unlikely. The behavior they warn results from an error in the standard does not apply.
Potatoswatter