0

I would like to make the example below compile. I have a class template which defines a friend function template, but it's not visible in the surrounding namespace by default.

namespace ns {
template<typename T> struct Foo {  // Class template.
    template<typename U> friend void bar(Foo, Foo<U> f) {  // Friend function template, defined inline.
        static_cast<void>(f.private_field);  // Should only compile when T=U.
    }
private:
    int private_field{};
};
}

int main() {
    bar(ns::Foo<char>{}, ns::Foo<char>{});      // Ok.
    ns::bar(ns::Foo<char>{}, ns::Foo<char>{});  // (1) FIXME: `ns::bar` not found.
    //bar(ns::Foo<bool>{}, ns::Foo<char>{});    // (2): Should fail because bar() here is not a friend of Foo<char>.
}

I would like to be able to call ns::bar<int>(ns::Foo<char>{}) ((1) should compile) while not declaring any friendship between bar call and unrelated Foo<char> at (2). How do I make this happen?

If Foo was a non-template, I would declare template<typename U> void bar(Foo, Foo); in namespace ns.

If bar was a non-template (e.g. with U=char fixed), I would declare it as a template function outside the class and befriend its full specialization, like here: friend void bar<T>(Foo, Foo<char> f);.

However, both are templates and I'm out of ideas.

UPD: I've tried giving the same trick as with non-template bar and make it a template outside of the class. However, it looks like befriending a partial specialization is not possible.

My attempt:

namespace ns {
template<typename T> struct Foo;

template<typename U, typename T> void bar(Foo<T>, Foo<U>);

template<typename T> struct Foo {
    template<typename U> friend void bar<U, T>(Foo, Foo<U> f);
    // template<typename U, typename TT> friend void bar(Foo<TT>, Foo<U> f);  // Compiles, but gives extra friendship to `bar`.
private:
    int private_field{};
};

template<typename U, typename T> void bar(Foo<T>, Foo<U> f) {
    static_cast<void>(f.private_field);  // Should only compile when T=U.
}
}

int main() {
    bar(ns::Foo<char>{}, ns::Foo<char>{});      // Ok.
    ns::bar(ns::Foo<char>{}, ns::Foo<char>{});  // (1) FIXME: `ns::bar` not found.
    //bar(ns::Foo<bool>{}, ns::Foo<char>{});    // (2): Should fail because bar() here is not a friend of Foo<char>.
}

GCC's output:

x.cpp:7:38: error: invalid use of template-id 'bar<U, T>' in declaration of primary template
    7 |     template<typename U> friend void bar<U, T>(Foo, Foo<U> f);
      |                                      ^~~~~~~~~
x.cpp: In instantiation of 'void ns::bar(ns::Foo<T>, ns::Foo<U>) [with U = char; T = char]':
x.cpp:19:41:   required from here
x.cpp:14:25: error: 'int ns::Foo<char>::private_field' is private within this context
   14 |     static_cast<void>(f.private_field);  // Should only compile when T=U.
      |                       ~~^~~~~~~~~~~~~
x.cpp:10:9: note: declared private here
   10 |     int private_field{};
      |         ^~~~~~~~~~~~~
yeputons
  • 8,478
  • 34
  • 67

1 Answers1

0

I think it's unsalvigable without a bridge function because of template ids etc. In your first approach, all your inner bars are:

for every T in Foo<T>:

template<typename U>
void bar(Foo<T>, Foo<U>);

so every Foo<T> produces it's own independant template overload of bar. That works for ADL, but does not work for qualified lookup, since friend declaration does not inject it's name into the namespace, see N0777 from 1995. It's fully disqussed in related question.

Adding one shared declaration like this:

template<typename T, typename U>
void bar(Foo<T>, Foo<U>);

would not work, because this template id is different from above. It compiles, but yields linkage errors (because t<T, U> bar(Foo<T>, Foo<U>) is not defined anywhere).

Your second approach has a similar problem: your code tried to define a new function because of it's template id and fails to do that (hence invalid use of template-id in declaration of primary template).

Your third attempt actually declared a friend specialization of bar (and you have exactly 1 template-id), but as you state, it gives away too much.

I you really want to pursue incapsulation, I would recommend using a bridge function with separate template id:

namespace ns {

    template<typename T> struct Foo {
        template<typename U> friend void bar_(Foo<T>, Foo<U> f) { // bar_ here
            static_cast<void>(f.private_field);
        }
    private:
        int private_field{};
    };

    template<typename T, typename U> void bar(Foo<T> x, Foo<U> f) { // end user gets a good name
        bar_(x, f); // ADL overload resolution, goes looking for friends and such.
    }

}

Also adding some type-dispatching arguments while keeping name bar should work too.

Lapshin Dmitry
  • 1,084
  • 9
  • 28