Reputation: 6584
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
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
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
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
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)};
}
Upvotes: 1