3

As a continuation to my last question, I'm now in the situation where I want to declare a function as a friend that depends on a mix of class template parameters (R) AND additional template parameters (Cb). How do I specify this correctly within class context? My naive approach doesn't seem to work:

Demo

#include <memory>
#include <future>
#include <cstdio>

template <typename Fn, typename R, typename... Args>
concept invocable_r = std::is_invocable_r<R, Fn, Args...>::value;

/* header.hpp */

template <typename T>
class entity;

template <typename R, invocable_r<R> Cb>
auto create_entity(Cb&& fn) -> std::shared_ptr<entity<R>>;

template <typename R>
struct entity
{
    template <invocable_r<R> Cb>
    entity(Cb&& fn)
        : fn_{ std::move(fn) }
    {}

    // *************** Q ***************
    // How to match this declaration to the outside declaration?

    template<invocable_r<R> Cb>
    friend auto create_entity<R>(Cb&& fn) -> std::shared_ptr<entity<R>>;

    // *************** /Q ***************

    std::packaged_task<R()> fn_;
};

/* impl.cpp */

template <typename R, invocable_r<R> Cb>
auto create_entity(Cb&& fn) -> std::shared_ptr<entity<R>> {
    return std::make_shared<entity<R>>(std::forward<Cb>(fn));
}

int main()
{
    create_entity<int>([]{ printf("Hello"); return 10; });
}

Error:

<source>:28:17: error: invalid use of template-id 'create_entity<R>' in declaration of primary template
   28 |     friend auto create_entity<R>(Cb&& fn) -> std::shared_ptr<entity<R>>;
      |                 
glades
  • 3,778
  • 1
  • 12
  • 34
  • AFAIK, its impossible since partial specialization of functions is not allowed and you trying to create such specialization when only first template argument is defined. – sklott Jan 19 '23 at 14:44
  • @sklott Well there must be a way, I can't imaginge that this is not possible at all – glades Jan 19 '23 at 15:12

1 Answers1

0

The problem here is that partial specializations are not allowed in friend declarations (see https://en.cppreference.com/w/cpp/language/friend#Template_friends) (or for function templates in general).

I was able to make it work using the following steps:

  1. We move the code from create_entity into a struct with a function call operator:
template <typename R, invocable_r<R> Cb>
struct create_entity_s {
    template<typename U = Cb>
    create_entity_s(U&& fn) : m_fn{ std::forward<U>(fn) } {}

    auto operator()() {
        auto ret = std::make_shared<entity<R>>(std::move(m_fn));
        // Test if we're really a friend by accessing a private member
        ret->_i = 5;
        return ret;
    }

    Cb m_fn;
};
  1. Now we define create_function as follows, returning an instance of the above struct:
template <typename R, invocable_r<R> Cb>
auto create_entity(Cb&& fn) {
    return create_entity_s<R, Cb>{ std::forward<Cb>(fn) };
}
  1. Change the friend declaration. Here, by using U, we make sure we don't partially specialize. However, this has the downside that we're now friends with every instantiation of create_entity_s:
template<typename U, invocable_r<U> Cb>
friend struct create_entity_s;
  1. Use create_entity (note the parantheses at the end in order to invoke the function object):
create_entity<int>([]{ printf("Hello"); return 10; })();

Here's the full working example on Compiler Explorer: https://godbolt.org/z/MPKe5Me6v

I haven't been able to make it work using the function template only, but the trick with the struct definitely works. Also I didn't spend an extreme amount of time on this, so please excuse any imperfections, but the general principle works!

Hope this helps!

Matthias Grün
  • 1,466
  • 1
  • 7
  • 12