Dmitry Kuzminov
Dmitry Kuzminov

Reputation: 6584

Friend template function instantiations that match parameter

I have a template class that should have a friend: a make_object function that allows to deduct certain types and values. I wish to make friends only those instantiations that match the types of the template class. The simplified version of my code is below:

template<size_t S, typename T>
class Object {
private:
    Object(T t);

    template<typename U, typename... Args>
    friend auto make_object(U, Args... args);
};


template<typename U, typename... Args>
inline auto make_object(U u, Args... args)
{
    constexpr size_t S = sizeof...(Args);
    return std::unique_ptr<Object<S, U>>(new Object<S, U>(u));
}

Taking this code as an example I wish to make friends only those instantiations of make_object whose typename U matches the typename T of the object. Is that even possible?

Upvotes: 2

Views: 290

Answers (4)

jfMR
jfMR

Reputation: 24778

As far as I understand, make_object() is a convenience function template that takes advantage of template argument deduction for creating Object objects.

You can create an additional function template, e.g., create_object(), that corresponds to the same template parameters as the Object class template: size_t and a type template parameter. Then, you can easily grant friendship in Object to an instance of this function template that has the same template arguments as Object:

template<size_t, typename>
class Object;

template<size_t S, typename T>
inline auto create_object(T t) {
   return std::unique_ptr<Object<S, T>>(new Object<S, T>(t));
}

template<size_t S, typename T>
class Object {
private:
    Object(T t);
    friend auto create_object<S, T>(T);
};

Your original make_object() function template just delegates to this new function template, create_object():

template<typename U, typename... Args>
inline auto make_object(U u, Args... args)
{
    constexpr size_t S = sizeof...(Args);
    return create_object<S, U>(std::move(u));
}

Once you know the number of elements in the parameter pack Args, you don't need the parameter pack any longer. You only pass the deduced U and the calculated S as template arguments to create_object().

Upvotes: 0

super
super

Reputation: 12968

If you want to have a friend template, you can make all the instatiations of the template a friend, like in your example, or you can make a full specialization a friend.

There is nothing in between unfortunately.

To work around that you could wrap your make_object function in a template class and make that template class a friend.

#include <type_traits>
#include <iostream>
#include <memory>

template<typename U>
struct object_maker;

template<std::size_t S, typename T>
class Object {
private:
    Object(T) {}

    friend struct object_maker<T>;
};


template<typename U>
struct object_maker
{
    template<typename... Args>
    static auto make_object(U u, Args... args)
    {
        constexpr std::size_t S = sizeof...(Args);
        return std::unique_ptr<Object<S, U>>(new Object<S, U>(u));
    }
};

int main()
{
    auto obj = object_maker<int>::make_object(7);
    static_assert(std::is_same_v<decltype(obj), std::unique_ptr<Object<0,int>>>);
}

Upvotes: 2

HTNW
HTNW

Reputation: 29193

You can use a class to separate the U parameter of make_object (which has to match with the T of Object) from the Args parameter pack, which does not have to match. Then, befriend the helper class, giving all instantiations of your function access to the private members of Object.

template<typename U>
struct make_object_t;

template<std::size_t S, typename T>
class Object {
private:
    Object(T t);

    friend class make_object_t<T>;
};

template<typename U>
struct make_object_t {
    template<typename... Args>
    static auto make_object(U u, Args... args) {
        constexpr std::size_t S = sizeof...(Args);
        return std::unique_ptr<Object<S, U>>(new Object<S, U>(u));
    }
};

Finally, write a helper function to hide the class from the API:

template<typename U, typename... Args>
auto make_object(U&& u, Args&&... args) {
    return make_object_t<U>::template make_object<Args...>(std::forward<U>(u), std::forward<Args>(args)...);
}

Now, e.g. this works

int main() {
    std::unique_ptr<Object<3, int>> ptr = make_object(5, 'a', 0x0B, "c");
}

But, e.g. make_object_t<char> cannot use Object<S, int>::Object:

template<>
struct make_object_t<char> {
    template<typename... Args>
    static auto make_object(char u, Args... args) {
        constexpr std::size_t S = sizeof...(Args);
        return std::unique_ptr<Object<S, int>>(new Object<S, int>(u));
    }
};
int main() {
    // error here from instantiation of above function template
    auto oops = make_object('n', 'o');
}

Upvotes: 1

ph3rin
ph3rin

Reputation: 4896

If your requirement is simply befriending a function template with the same template parameter as T, then this would be sufficient:

#include <cstdint>

template <typename T, typename ... TArgs>
auto make_object(T, TArgs...);

template<std::size_t size, typename T>
class Object {
private:
    Object(T t);
    friend auto make_object<T>(T);
};

template <typename T, typename ... TArgs>
auto make_object(T t, TArgs... args) {
    Object<0u, T>{t};   // Compiles
}

template <>
auto make_object<float>(float f) {
    // Error: Constructor is private
    Object<0u, int>{static_cast<int>(f)};  
}

Compiler Explorer

Upvotes: 1

Related Questions