Suppose I have the following template class that defines a nested class:
template <typename T>
struct foo {
struct bar { };
};
Suppose the environment I'm coding in also has the following helper class, which should be specialized for any type that needs special handling:
template <typename T>
struct maybeChangeType { using type = T; } /* default: same type */
How can I specialize maybeChangeType
for foo<T>::bar
? It's easy enough to specialize for, say, foo<int>::bar
, but foo
will be used with 100+ different T
so that's not really an option.
NOTE: Please read carefully before marking this question as a duplicate. This question is not asking how to specialize in general (e.g. Understanding templates in c++), or how to declare friends, or even how to declare friends of templates. It is asking how to declare a friend for a non-template nested member of a template class (as the title states).
Trying to define specializations in the "normal" way does not work because foo<T>::bar
is not a deducible context (bad sign: it needs typename
in front):
/* error: template parameters not deducible in partial specialization */
template <typename T>
struct maybeChangeType<typename foo<T>::bar>;
Declaring the specialization as a friend also produces compilation errors:
template <typename T>
struct foo {
struct bar {
/* errors:
* - class specialization must appear at namespace scope
* - class definition may not be declared a friend
*/
template <>
friend struct maybeChangeType<bar> { using type=T; };
};
};
The above errors make it clear that the actual definition of these friends has to be out of line:
template <typename T>
struct foo {
struct bar {
friend struct maybeChangeType<bar>;
};
};
But now we're back where we started: any attempt to define a specialization for foo<T>::bar
will fail because it uses bar
in a non-deducible context.
NOTE: I can work around the issue for functions by providing a friend overload inline, but that's no help for a class.
NOTE: I could work around the issue by moving the inner class out to namespace scope, but that would pollute the namespace significantly (lots of inner classes the user really has no business playing with) and complicate the implementation (e.g. they would no longer have access to private members of their enclosing class and the number of friend
declarations would proliferate).
NOTE: I understand why it would be dangerous/undesirable to allow arbitrary specialization of the name foo<T>::bar
(what if foo<T>
had using bar = T
for example), but in this case bar
really is a class (not even a template!) which foo really does define, so there shouldn't be any ODR hairiness or risk that the specialization would affect other (unintended) types.
Ideas?
Friend functions can be defined (given a function body) inside class declarations. These functions are inline functions. Like member inline functions, they behave as though they were defined immediately after all class members have been seen, but before the class scope is closed (at the end of the class declaration).
Many-to-one: All instantiations of a template function may be friends to a regular non-template class. One-to-one: A template function instantiated with one set of template arguments may be a friend to one template class instantiated with the same set of template arguments.
Friend class is the functionality of C++, which is used to access the non-public members of a class. Java doesn't support the friend keyword, but we can achieve the functionality.
A friend function of a class is defined outside that class' scope but it has the right to access all private and protected members of the class. Even though the prototypes for friend functions appear in the class definition, friends are not member functions.
As an intrusive solution, it is possible to use functions for type programming (metaprogramming). You could write the type function as a friend function:
template<typename T> struct type_t { using type = T; };
template <typename T>
struct foo {
struct bar {
friend constexpr auto maybeChangeType_adl(type_t<bar>) -> type_t<T>
{ return {}; }
};
};
Replace type_t<T>
with any type_t<my_type_function_result>
. It is not necessary, but sometimes convenient, to define this function, e.g. for computations in constant expressions. type_t
can be enhanced with comparison operators, to replace std::is_same<A, B>
with infix a == b
for example. The type type_t<T>
is used instead of T
directly for two reasons:
maybeChangeType_adl
, it is necessary to
construct an object of the return type.In C++14, I'd use variable templates to implement this function:
template<typename T> constexpr auto type = type_t<T>{};
// ...
friend constexpr auto maybeChangeType_adl(type_t<bar>) { return type<T>; };
Though this loses some of the symmetry (parameter vs return type).
In any case, you can query the type as follows:
template<typename T> using inner_type = typename T::type;
template<typename T> using maybeChangeType =
inner_type<decltype(maybeChangeType_adl(type_t<T>{}))>;
A fall-back function maybeChangeType
can be provided to mirror the primary template in the OP:
template<typename T> auto maybeChangeType(type_t<T>) -> type_t<T> { return {}; }
Or, you specialize a maybeChangeType
class template on the existence of the maybeChangeType_adl
function:
template<typename T, typename = void>
struct maybeChangeType { using type = T; };
template<typename T>
struct maybeChangeType<T, void_t<decltype(maybeChangeType_adl(type_t<T>{}))>>
{ using type = inner_type<decltype(maybeChangeType_adl(type_t<T>{}))>; };
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With