Search code examples
c++templatesfriend

Template friend function of two typenames for a template class


I try to make a template function of two argument typenames a friend of a template class. But i cannot manage to make it link.

Consider following example. Note, that the code is extremely simplified.

#include <iostream>

template <typename T>
struct S;

template <typename U, typename T>
S<U> cast(S<T> const &); //PROBLEMATIC!

template <typename T>
std::ostream& operator<< (std::ostream& os, S<T> const & s); //no problem

template <typename T>
struct S
{
public:
    S() = default;
    ~S() = default;
    S( S const & ) = default;
    S( S&& ) = default;
    S(T val) : value{val} {}

    template <typename U>
    friend
    S<U> cast(S<T> const &); //PROBLEMATIC!

    friend
    std::ostream& operator<< <T>(std::ostream& os, S<T> const & s); //no problem
private:
    T value;
};

template <typename T>
inline std::ostream& operator<< (std::ostream& os, S<T> const & s) //no problem
{ return os << s.value; }

template <typename T, typename U>
S<U> cast(S<T> const & src) //problematic!
{return {static_cast<U>(src.value)};}

int main()
{
    S<int> s1{ 10 };
    auto s2{ cast<float>(s1) }; //linker error
    std::cout << s1 << ' ' << s2;
    return 0;
}

So, simply put, cast is a glorified conversion function. It cannot be rewritten as a regular conversion function in original design.

Notice, that template operator<< gets explicitly specialized, so there is no problem. However, cast can only be partially specialized, which is (AFAIK) not allowed for functions.

The question is: How (if) I can achieve this without explicitly instantiating S<float>?

I have tried removing forward declaration of cast before S. It did not help. I have tried template <typename U> friend S<U> cast<U>(S<T> const &);. It raises partial specialization not allowed.

Exact linker errors:

<source>:26:10: error: function template partial specialization is not allowed
   26 |     S<U> cast<U>(S const &);
      |          ^   ~~~
1 error generated.
/opt/compiler-explorer/gcc-14.2.0/lib/gcc/x86_64-linux-gnu/14.2.0/../../../../x86_64-linux-gnu/bin/ld: /tmp/example-fd62b9.o: in function `main':
<source>:45:(.text+0x22): undefined reference to `S<float> cast<float>(S<int> const&)'
clang++: error: linker command failed with exit code 1 (use -v to see invocation)

live example


Solution

  • This is a working code

    #include <iostream>
    
    template <typename T>
    struct S;
    
    template <typename T>
    std::ostream& operator<<(std::ostream& os, S<T> const& s);  // no problem
    
    template <typename T>
    struct S {
     public:
      S(T val) : value{val} {}
    
      template <typename U, typename V>
      friend S<U> cast(S<V> const&);
    
      friend std::ostream& operator<< <T>(std::ostream& os, S<T> const& s);
    
     private:
      T value;
    };
    
    template <typename T>
    inline std::ostream& operator<<(std::ostream& os, S<T> const& s) {
      return os << s.value;
    }
    
    template <typename U, typename T>
    S<U> cast(S<T> const& src) {
      return {static_cast<U>(src.value)};
    }
    
    int main() {
      S<int> s1{10};
      auto s2{cast<float>(s1)};
      std::cout << s1 << ' ' << s2;
      return 0;
    }
    

    https://godbolt.org/z/jTxrn96zz

    1. The forward declaration of the friend cast is unnecessary.
    2. For matching the friend declaration with the implementation, the friend must be declared with two template parameters.
        template <typename U, typename V>
        friend S<U> cast(S<V> const&);
      
    3. The template parameters are swapped, you have tried this correctly but the friend declaration did not match the implementation.