5

We observed a surprising behaviour in our code base, where a friendship relation was failing to be applied. (It is currently compiled only with Clang, version 3.6)

We could reduce it to this minimal example. Let's imagine we have the following template class definitions:

template <int>
class Element
{};


// Forward declaration of FriendBis
template <template <int> class> class FriendBis;

class Details
{
    friend class FriendBis<Element>;
    int mValue = 41;
};

template <template <int> class>
class FriendBis
{
public:
    void useDetails(const Details &aDetails)
    {
        aDetails.mValue;
    }
};

Here, Details declares that the instantiation of FriendBis with its single template template parameter substituted with Element is its friend. Because of that, the following client code compiles successfully:

FriendBis<Element> fb1;
fb1.useDetails(Details());

 The problem

Now, let's introduce the additional trait templated type, whose whole purpose is to define proto as a template alias for the Element template:

struct trait
{
    template <int N>
    using proto = Element<N>;
};

The client code below does not compile:

FriendBis<trait::proto> fb2;
fb2.useDetails(Details());

It is surprising to us, because trait::proto is an alias for Element, but one compiles while the other does not.

  • Is this an expected behaviour ?
    • If so, what is the rationale for this restriction ?
    • Is there a workaround ? (while maintaining a restricted friendship, instead of making all instantiations of FriendBis a friend).
Rob
  • 26,989
  • 16
  • 82
  • 98
Ad N
  • 7,930
  • 6
  • 36
  • 80
  • 1
    This is related: http://stackoverflow.com/a/30654483/4326278. Maybe the details in there will help. – bogdan Nov 06 '15 at 17:32

1 Answers1

2

An alias template is not synonymous with the type it aliases: trait::proto and Element are disparate types. When a template-id refers to the specialization of trait::proto then it is equivalent to the substituted type. Simply put, trait::proto is not Element, but trait::proto<0> is Element<0>.

In response to your questions:

  • Yes, this is expected behaviour

  • The rationale is that the aliased type could be far more complex than just Element<N>, it could be something like Element<ElementForInt<N+1>::value>. The mapping then is non-obvious.

  • I can't think of a workaround off the top of my head. If you want to check if a template template parameter is the same as some other template and account for alias templates, you can check if instantiations of the two name the same type, e.g. std::is_same<T<0>, Element<0>>, but I'm not sure how you would make that work in a friend declaration.

TartanLlama
  • 63,752
  • 13
  • 157
  • 193
  • 2
    This is [CWG 1286](http://wg21.link/CWG1286). Also, `trait::proto` and `Element` are not types. – T.C. Nov 02 '15 at 18:06
  • Thank you for answering ! I am failing to understand the example you are giving for the rationale. Could you please elaborate a bit regarding why it is then non-obvious ? @TC Thank you for the link. – Ad N Nov 03 '15 at 11:13