1

I don't understand this link error. I have 2 classes:

#include "Vector3.h"
#include "Quaternion.h"

template<typename T>
class Point3 final
{
public:
   constexpr Point3(const Vector3<T>& vec)
   : x(vec.x), y(vec.y), z(vec.z)
   {}

   constexpr operator const Vector3<T>() const
   {
      // It is the equivalent of Vector3 = Point3 - Origin
      return Vector3<T>(x, y, z);
   }

  constexpr operator Vector3<T>() const
  {
     // It is the equivalent of Vector3 = Point3 - Origin
     return Vector3<T>(x, y, z);
  }

  T x = T(0);
  T y = T(0);
  T z = T(0);

  friend Vector3<T>;
  friend Quaternion<T>;

  friend Vector3<T> operator*( const Quaternion<T>& lhs, const Vector3<T>& rhs);
  friend Vector3<T> operator*( Vector3<T> lhs, const Vector3<T>& rhs);
};

typedef Point3<Float32> Point3f;

and

template<typename T>
class Vector3 final
{
public:

  constexpr Vector3()
  {}

  constexpr Vector3(T _x, T _y, T _z)
  : x(_x), y(_y), z(_z)
  {}

  T x = T(0);
  T y = T(0);
  T z = T(0);

};

typedef Vector3<Float32> Vector3f;

I also have a Quaternion class the detail are irrelevant i beleive but this class has a non member operator*:

 template<typename T>
 Vector3<T> operator*( const Quaternion<T>& lhs, const Vector3<T>& rhs)
 {
    // nVidia SDK implementation
    Vector3<T> qvec(lhs.x, lhs.y, lhs.z);
    Vector3<T> uv = cross(qvec, rhs) * T(2.0) * lhs.w;    //uv = qvec ^ v;
    Vector3<T> uuv = cross(qvec, uv) * T(2.0);    //uuv = qvec ^ uv;
    return rhs + uv + uuv;
 }

Now those line produce a link error, but why?

Math::Point3<Float32> pt = -Math::Point3<Float32>::UNIT_Z;
Math::Vector3<Float32> vec = orientation_*pt; // link error here (orientation is a Quaternion<Float32>)
//Math::Vector3<Float32> vec = orientation_*Math::Vector3<Float32>(pt); // this solve the link error.

Here is the link error

Undefined symbols for architecture x86_64:
  Math::operator*(Math::Quaternion<float> const&, Math::Vector3<float> const&), referenced from:
  GfxObject::Procedural::BoxGenerator::addToTriangleBuffer(GfxObject::Procedural::TriangleBuffer&) const in ProceduralBoxGenerator.o

Update

I found 2 question that are really close to this but the problem relies in the differences.

in: question 1 and question 2

But in my case I need to convert between 2 templates classes instead of the same class but 2 instantions. I hope this will help!

Community
  • 1
  • 1
monamimani
  • 273
  • 2
  • 11
  • 2
    You do **not** have two classes plus a Quaternion class. You have three **templates**. – Pete Becker Jun 13 '13 at 02:52
  • There needs to be at least one instantiation of the templated `operator*`. It should either be placed in a header file included by the source file that uses it or explicitly instantiated in the source file containing the `operator*` – Captain Obvlious Jun 13 '13 at 02:52
  • @PeteBecker Yes I have 3 template classes. But I beleive the problem arise from the 2 template classes Vector3 and Point3. – monamimani Jun 13 '13 at 03:02
  • @CaptainObvlious I am not sure what you are talking about. The compiler will instantiated what is needed no?. Thus this orientation_*pt will try to instantiate the operator* that has a Vecotr3 as second argument and since the Point3 class declared the operator as friend it will see it can convert a Point3 to a Vector3. – monamimani Jun 13 '13 at 03:06
  • 1
    The compiler needs the template function definition to be visible at the point of instantation. It's not clear whether the definition of `operator*` is in a header file or a source file. It needs to be in a header file that you include before you try to use it. – Raymond Chen Jun 13 '13 at 03:10
  • from the link error I was able to figure out that it can't find a symbole for the operator* but the right header file is inlcuded. – monamimani Jun 13 '13 at 03:12
  • @RaymondChen yes sorry, all this code is in header file beside the line that is actually causing the link error. – monamimani Jun 13 '13 at 03:13
  • This should not even compile. User-defined implicit conversions are never considered when resolving a template function call. Your `orientation_*pt` should fail, unless you have some other `operator*` that you are not showing. Try reducing your code to a minimal example that actually compiles, throw everything into a single .cpp file and post it here. – n. m. could be an AI Jun 13 '13 at 06:00
  • 1
    Can you create an [SSCCE](http://sscce.org/) for this? – MvG Jun 13 '13 at 08:28
  • @n.m. This compile but doesn't link. This is the minimal example. I reduce it to the line that is the culprit. When I comment the line where i do the multiplication it link successfully. I know user-defined implicit conversion are not considered when resolving template function call. That is why the function is friend with the class. If you look at the link I posted in the update they made it work. – monamimani Jun 13 '13 at 14:20
  • @MvG I don't think I can get shorter. If you want I can put the Quaternion class but it is trivial. It is like the Point3 or Vector3 class. – monamimani Jun 13 '13 at 14:22
  • It is not self-contained. Start from something self-contained and compilable, preferrably a single file, but two files if you need that to reproduce the issue. Strip out functionality as long as possible. Post what remains. Make sure which files are the same and which are different, which is the header and which the compilation unit. – MvG Jun 13 '13 at 14:37
  • An example of how to reduce this to a SSCCE: http://ideone.com/uMNYKM . Also, the first compiler warning there is why it does compile (despite appearances) but doesn't link. – aschepler Jun 13 '13 at 15:00
  • Is that `operator*` definition in Quaternion.h? – aschepler Jun 13 '13 at 15:03
  • http://www.parashift.com/c++-faq-lite/template-friends.html – aschepler Jun 13 '13 at 15:11

3 Answers3

1

Try making sure the compiler knows your friend declaration is supposed to be a template specialization, not a declaration of a brand new non-template function:

friend Vector3<T> operator* <> (const Quaternion<T>& lhs, const Vector3<T>& rhs);

This common mistake is discussed in the C++ FAQ here.

aschepler
  • 70,891
  • 9
  • 107
  • 161
0

Starting with an SSCCE:

template<typename T> class Quaternion { };
template<typename T> class Vector3 { };
template<typename T> class Point3 {
public:
  operator Vector3<T>() const { return Vector3<T>(); }
  friend Vector3<T> operator*( const Quaternion<T>& lhs, const Vector3<T>& rhs);
};
template<typename T>
Vector3<T> operator*(const Quaternion<T>& lhs, const Vector3<T>& rhs) { }
int main() {
  Quaternion<float> orientation_;
  Point3<float> pt;
  Vector3<float> vec = orientation_*pt;
  return 0;
}

Compiling that with gcc 4.7 I get the following:

x.cc:6:79: warning: friend declaration ‘Vector3<T> operator*(const Quaternion<T>&,
                    const Vector3<T>&)’ declares a non-template function
                    [-Wnon-template-friend]
x.cc:6:79: note: (if this is not what you intended, make sure the function template
                 has already been declared and add <> after the function name here) 
Undefined symbols for architecture x86_64:
  "operator*(Quaternion<float> const&, Vector3<float> const&)", referenced from:
      _main in ccKgI7Ru.o

This points out the problem: you are declaring a friend function, not a friend template. To fix this, you have to do two things: make sure the template declaration preceedes this friendship line, and add angle brackets:

template<typename T>
Vector3<T> operator*(const Quaternion<T>& lhs, const Vector3<T>& rhs) { }

template<typename T> class Point3 {
public:
  operator Vector3<T>() const { return Vector3<T>(); }
  friend Vector3<T> operator*<>(const Quaternion<T>& lhs, const Vector3<T>& rhs);
};

This will turn the linker error into a compiler error:

error: no match for ‘operator*’ in ‘orientation_ * pt’
note: candidate is:
note: template<class T> Vector3<T> operator*(const Quaternion<T>&, const Vector3<T>&)
note:   template argument deduction/substitution failed:
note:   ‘Point3<float>’ is not derived from ‘const Vector3<T>’

Which makes sense: declaring a friend is a statement about who may access what data; it does not help a bit in automatic type conversions for template resolution. So I suggest the following instead:

template<typename T> class Point3 {
public:
  operator Vector3<T>() const { return Vector3<T>(); }
  friend Vector3<T> operator*(const Quaternion<T>& lhs, const Point3& rhs) {
    return lhs * Vector3<T>(rhs);
  }
};

This declares a new inline friend operator, which will explicitely do the cast you had intended.

MvG
  • 57,380
  • 22
  • 148
  • 276
  • Thanks for showing me that, and for the SSCCE, it looked to me really bare bone until I saw yours. I actually found how to make it work. I don't like the solution in the end but it is the one I was trying to acheive, like in item 46 of effective C++. – monamimani Jun 13 '13 at 23:01
0

Actually to make it work i have to give a body to the friend declared function in the Point class. I wanted to do like item 46 of Effective C++ (3rd edition) from Scott Meyers. like in this question, link.

The key is that the function inside have to have a body. so the class Point3 now become (I have stripped some useless code like MvG did, thanks.

template<typename T> class Point3 {
public:
  operator Vector3<T>() const { return Vector3<T>(); }
  friend Vector3<T> operator*( const Quaternion<T>& lhs, const Vector3<T>& rhs)
  {
    return lhs*rhs;
  }  
};

This make everything compiles and link. lhsrhs will call the operator defined in Quaternion.h ( that is included in the header file of Point3).

What i don't understand is why i don't have duplicated symbol. To me the friend operator* inside the Point3 class and the operator* defined in the Quaternion.h header file are the same signature and yet it compile and links.

Community
  • 1
  • 1
monamimani
  • 273
  • 2
  • 11