8

I have a class template Foo<T>.

I'd like to implement a non-member function Bar that takes two Foos and returns a Foo. I want Bar to be a non-member because it will be more natural for callers to write Bar(f1, f2) than f1.Bar(f2). I also want Bar to be inline because the calculation is trivial and frequent.

template <typename T>
inline Foo<T> Bar(const Foo<T> &lhs, const Foo<T> &rhs) {
  ...
}

The trick is Bar needs access to Foo's private data. I'd prefer not to have accessors to the private data--there's no good reason to expose the private data to users. So I'd like to make Bar a friend of Foo.

template <typename T>
class Foo {
  ...
  private:
    T w, x, y, z;
    friend Foo<T> Bar(const Foo<T> &lhs, const Foo<T> &rhs);
};

Here's where I run into trouble. The compiler complains:

The inline specifier cannot be used when a friend declaration refers to a specialization of a function template.

Is this rule imposed by the standard or is it specific to MSVC++?

Here's what I've tried:

  • Make Bar a const public member function, and then to declare a non-member version that simply returns lhs.Bar(rhs). This seems the least hacky solution.

  • Remove the inline hint, knowing that the compiler is going to decide about inlining regardless of the hint. Does this then run afoul of the one-definition rule? It will still have to be defined in a header file because it's a function template.

  • Declare the member function with a dummy template type:

    template <typename T>
    class Foo {
      ...
      private:
        T w, x, y, z;
    
        // Note that this declaration doesn't actually use Dummy.  It's just there to
        // satisfy the compiler.     
        template <typename Dummy>
        friend Foo<T> Bar(const Foo<T> &lhs, const Foo<T> &rhs);
    };
    

I'm not entirely sure why that works, but it does satisfy the compiler.

Is there a better solution?

Adrian McCarthy
  • 41,073
  • 12
  • 108
  • 157

3 Answers3

5

If the calculation is trivial, I would write:

template <typename T>
class Foo {
  ...
  private:
    T w, x, y, z;
  public:
    friend Foo Bar(const Foo &lhs, const Foo &rhs) {
        ...
    }
};

This doesn't fall foul of the ODR - it's an inline function with external linkage (3.2/5 excludes this from the ODR subject to the definitions being identical, 7.1.2/3 says that it's inline).

However, this doesn't define a function template Bar<T>, it just defines a set of function overloads for Bar. There may be some reason, unstated in the question, that means this won't work for you because you actually need the template.

Steve Jessop
  • 257,525
  • 32
  • 431
  • 672
2

I like option 1 the best:

template <typename T>
inline Foo<T> Bar(const Foo<T> &lhs, const Foo<T> &rhs) {
    return lhs.Bar(rhs);
}

template <typename T>
class Foo {
  ...
    Foo<T> Bar(const Foo<T> &other) const;
  private:
    T w, x, y, z;
};

Then the functionality is safely contained within the class, but you provide a wrapper function for convenience.

Adrian McCarthy
  • 41,073
  • 12
  • 108
  • 157
Tim
  • 8,474
  • 3
  • 37
  • 54
1

Bar is a template, so it has to be a template in the friend declaration as well.

You don't necessarily have to use a dummy parameter, but could rather use

 template <typename U>
 friend Foo<U> Bar(const Foo<U> &lhs, const Foo<U> &rhs);

You cannot use T as the template parameter here, as there is already an outer T in scope.

Bo Persson
  • 86,087
  • 31
  • 138
  • 198
  • 1
    I tried that, but it doesn't seem right that Foo would declare a friend function for Bar where U is a different type than T. Possibly there's no real harm, but it felt wrong. That's how I ended up with the dummy parameter. – Adrian McCarthy Mar 14 '11 at 23:06
  • It might seem wrong, but currently (C++03) you cannot select your friends for a specific T. Your Dummy parameter can also be any type different from T. – Bo Persson Mar 14 '11 at 23:11