Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to define friends for classes defined inside template class

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?

like image 561
Ryan Avatar asked Aug 21 '15 16:08

Ryan


People also ask

Can friend function be defined inside class?

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).

Can a template class be a friend?

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.

How do you define a Friends class in Java?

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.

Can we define friend function outside the class?

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.


Video Answer


1 Answers

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:

  1. For a definition of maybeChangeType_adl, it is necessary to construct an object of the return type.
  2. Not all types can be returned from a function. E.g. abstract types, function types, array types. Those can still be used as template arguments.

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>{}))>; };
like image 68
dyp Avatar answered Oct 27 '22 15:10

dyp