0

I would like to define a templated structure with a friend function whose argument types are derived from types defined within the structure. The friend function should be callable without explicit type specification if the corresponding structure has been instantiated.

The following approach seems to work:

template <typename T> struct A {
    typedef T const& underlying_param_type;
    typedef A<T>& param_type;
    friend void mutateA(param_type a, underlying_param_type b) { a.data_ = b; }
    T data_;
};

If one defines the friend function with parameter types that do not depend on internals of the structure it is possible to separate interface and implementation as follows:

template <typename T> struct B;
template <typename T> void mutateB(B<T>& a, T const& b);

template <typename T> struct B {
    friend void mutateB <> (B<T>& a, T const& b);
    T data_;
};

template <typename T> void mutateB(B<T>& a, T const& b) { a.data_ = b; }

Now I am wondering whether both approaches can be combined. The following approach does not work (clang++ 3.3, g++ 4.8.2, -std=c++11):

template <typename T> struct C;
template <typename T> void mutateC(typename C<T>::param_type a, typename C<T>::underlying_param_type b);

template <typename T> struct C {
    typedef T const& underlying_param_type;
    typedef C<T>& param_type;
    friend void mutateC <> (typename C<T>::param_type a, typename C<T>::underlying_param_type b);
    T data_;
};

template <typename T> void mutateC(typename C<T>::param_type a, typename C<T>::underlying_param_type b) { a.data_ = b; }

int main() {
    A<int> a;
    mutateA(a, 1);

    B<int> b;
    mutateB(b, 1);

    C<int> c; // error: no function template matches function template specialization 'mutateC'
    mutateC(c, 1);

    return 0;
}

I guess that the last approach fails since template argument deduction does not work across ::. Any ideas?

precarious
  • 598
  • 2
  • 14
  • `<>` into ``? Maybe more, but your problem looks like syntax errors in your friend declaration. Start with a simpler friend declaration and get it to work? (where you friend a particular specialization) – Yakk - Adam Nevraumont Dec 22 '13 at 14:26
  • After changing <> into the statement C c; compiles but mutateC(c, 1) does not. mutateC(c, 1) works in this setting but this is not sufficient for my use case. – precarious Dec 22 '13 at 14:39
  • Ah yes. For arbitrary `typedef` what you ask for requires inverting a turing complete computation, so is infeasible. Provide more details, as special cases may be feasible. The reason why your first one works is that ADL finds the friends of the `A` argument, as an example. The types passed in your non workimg example do not help ADL, and to do so you would need invert a `template` substitution over all types. A manual inversion that you write can do this with a helper function. – Yakk - Adam Nevraumont Dec 22 '13 at 14:47
  • You are correct that the last approach fails because the arguments are non-deducable. Why do you want to do this? What's wrong with `friend void mutateA(A& a, const T& b) { a.data_ = b; }` or even better, `friend void mutateA(A& a, T b) { a.data_ = std::move(b); }`? – Casey Dec 22 '13 at 20:19
  • In my actual use case I have many friend functions of similar structure. Particularly, there are a few argument types only which are derived from T. The derivation rules might change in the future. The typedefs were introduced in order to make such changes easy. The only drawback I see in the working solution (struct A) is that interface and implementation are not separated. I was wondering whether there is a way to separate those. If such a solution would just trade one uglyness for another I might just keep going with what I have. In any case, it would be interesting to see other solutions. – precarious Dec 22 '13 at 21:14

2 Answers2

1

Make two little changes:

  • friend void mutateC ...
  • mutateC< int >(c, 1);
  • Thank you. "The friend function should be callable without explicit type specification if the corresponding structure has been instantiated." – precarious Dec 22 '13 at 14:15
0

Adding a level of indirection solves the problem:

template <typename T> struct C;
template <typename T> void mutateC_impl(typename C<T>::param_type a, typename C<T>::underlying_param_type b);

template <typename T> struct C {
    typedef T const& underlying_param_type;
    typedef C<T>& param_type;
    friend void mutateC(typename C<T>::param_type a, typename C<T>::underlying_param_type b) { mutateC_impl<T>(a, b); }
    friend void mutateC_impl<T>(typename C<T>::param_type a, typename C<T>::underlying_param_type b);
    private: T data_;
};

template <typename T> void mutateC_impl(typename C<T>::param_type a, typename C<T>::underlying_param_type b) { a.data_ = b; }
precarious
  • 598
  • 2
  • 14