Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why would an Alias Template be treated differently than the aliased type template when it comes to friendship?

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).
like image 445
Ad N Avatar asked Oct 30 '22 16:10

Ad N


1 Answers

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.

like image 152
TartanLlama Avatar answered Nov 15 '22 04:11

TartanLlama