2

Please excuse me if this is asked elsewhere (and provide a link!). I wasn't able to find an exact duplicate.

Not sure if I'm even phrasing the Q correctly.

As a minimal example:

#include <cstdint>
#include <vector>

class Priv {
protected:
    // All members of Priv are protected and should not be accessed directly.
    using Typ = uint16_t;
    //   (In reality, Typ is defined based on various macro definitions.)
    // ...
    // However, I would like to give access to classes I specifically befriend.
    // All instantiations of Public should be permitted, regardless of T.
    template <typename T> friend struct Public;
};

// Default arguments in class Public's template parameter list should be able to 
// access protected members of Priv because Public has been friend-zone'd, right?
template <typename T = Priv::Typ>
struct Public: std::vector<T> {
    inline Public(): std::vector<T>() {}
};

int main() {
        Public pub; // should default to Public<Priv::Typ>
}

But unfortunately, I seem to be declaring the friend incorrectly:

<source>:17:30: error: 'using Typ = uint16_t' is protected within this context
   17 | template <typename T = Priv::Typ>
      |  

Can someone point out my mistake?

ardnew
  • 2,028
  • 20
  • 29
  • Since you're clearly in control of `Priv`, what would be wrong with declaring `Typ` as public? – paddy Jul 30 '23 at 22:14
  • 1
    Add a `public` method to access `Typ` or just make it `public`... Or maybe there's more to this question that you are not telling us... – Jesper Juhl Jul 30 '23 at 22:17
  • 1
    The default template argument is _outside_ the class scope, not inside it. It makes `Public<>` to be equivalent to `Public` where clearly the argument is used in the context of the user, not the class. So it _shouldn't_ be possible to use a `protected` member in this context. What is conceptionally the reason for the member to be protected, but still being supposed to be usable _only_ as implicit default argument? – user17732522 Jul 30 '23 at 22:17
  • 2
    This has nothing to do with templates. The same basic issue would also occur with an analogous attempt to inherit from a non-template class. – Sam Varshavchik Jul 30 '23 at 22:18
  • Your title is about a private members, the code is about a protected member. – 273K Jul 30 '23 at 22:29
  • I thought `Friend` was to provide access to protected or private members? Maybe I should have made it clearer that I'm trying to make `Public` a friend to override the access constraint (line 12 of my example) – ardnew Jul 30 '23 at 22:58
  • It's a fine line we walk between "minimal _working_ example" and "withholding information"… – ardnew Jul 30 '23 at 23:06
  • @SamVarshavchik: A friend _can_ inherit from a base named with a name it can access only because it is a friend ([class.friend]/2). – Davis Herring Jul 30 '23 at 23:08

1 Answers1

1

The default template argument is outside the class scope, not inside it. It makes Public<> to be equivalent to Public<Priv::Typ> where clearly the argument is used in the context of the user, not the class. So it shouldn't be possible to use a protected member in this context.

You can only use the private/protected members of the class inside the friend's scope. So if you want the user of the class template to be able to specify the type either implicitly or explicitly while still having Typ marked protected, then you need to do a translation to Typ somehow inside the class scope. For example:

struct PrivTypProxy;

template <typename T = PrivTypProxy>
struct Public:
    std::vector<
        std::conditional_t<
            std::is_same_v<T, PrivTypProxy>,
            Priv::Typ,
            T
         >
      >
{
    using U = std::conditional_t<
                  std::is_same_v<T, PrivTypProxy>,
                  Priv::Typ,
                  T
               >;
    inline Public(): std::vector<U>() {}
};

But it is not clear to me what the intent for doing this is.

user17732522
  • 53,019
  • 2
  • 56
  • 105
  • You say `So it shouldn't be possible to use a protected member in this context.` because `Typ` is protected. But what is the point of `Friend` if not to give access to individual classes that otherwise wouldn't (due to `protected`, `pritvate`, etc.)? – ardnew Jul 30 '23 at 22:56
  • 1
    @ardnew The point was that in `Public` it's not a `Public` instance that tries to access `Priv::Typ`, it's the outside user that tries to instantiate a class based on the class template `Public` using `Priv::Typ`. – Ted Lyngmo Jul 30 '23 at 23:07
  • @ardnew The default template argument is not part of any class. It is used only to form a class template specialization (which then itself is a class that is friended) by the user of the template. – user17732522 Jul 30 '23 at 23:25
  • @ardnew Asked the other way around: Do you expect the user to be allowed to write `Public pub;`? If not, what is supposed to be the conceptional difference to `Public pub;` or `Public<> pub;`? – user17732522 Jul 30 '23 at 23:28
  • @user17732522 so is there an appropriate way to friend the default template arguments? Something similar to the EDIT at the end of this question: https://stackoverflow.com/a/702701/1054397 – ardnew Jul 30 '23 at 23:28
  • @ardnew That's a completely different situation/problem. I don't see the connection to your question. Maybe start by explaining what you are ultimately trying to achieve with this `friend` scheme. – user17732522 Jul 30 '23 at 23:29
  • @user17732522 no, the user won't know what `Priv::Typ` is (without some digging into CPU reference manuals). It is there merely as a default when they do not provide their own type as parameter – ardnew Jul 30 '23 at 23:30
  • 1
    @ardnew Knowing or not knowing what type `Priv::Typ` aliases is a completely orthogonal problem from whether or not the user is supposed to be allowed to use the alias. You seem to intent the user to _not_ be allowed to explicitly name the alias, but still to implicitly name it via template argument deduction. It is not clear to me why you need to restrict the explicit use. – user17732522 Jul 30 '23 at 23:33
  • @user17732522 right, the user should _not_ be allowed to name or refer explicitly to the alias, because that alias refers to a system-specific value. If the user were to continue using the alias, they would be circumventing an interface that ensures portable code. You can think of `Priv` as target hardware details, and `Public` as a hardware abstraction layer – ardnew Jul 30 '23 at 23:41
  • @ardnew Default (template) arguments are only syntactical sugar to automatically add to (template) argument lists for the user of the entity. They are neither part of the public interface nor an implementation detail of the entity. Any user can redeclare templates and functions to add default arguments, possibly inconsistently in different translation units. So if you really want to hide the alias itself this way `friend` is not enough. You need something like my suggestion in the answer. – user17732522 Jul 30 '23 at 23:48
  • 1
    @ardnew I don't think there's much you can do about it without changing your design. You asked what your mistake was and got the answer. I would just accept the answer and rethink the design. – Ted Lyngmo Jul 30 '23 at 23:51
  • @TedLyngmo My Q is requesting a solution in C++ language syntax terms regarding friend access to protected members of another class. Thus, I feel an adequate A must either: 1. Modify the syntax to produce something semantically equivalent, or 2. Explain why I cannot (or shall not) use these language constructs in this case. This A — while very thoughtful and _being considered_ — posits an entirely novel interface solution, effectively ignoring and dismissing the central issues in the Q. This answer does not meet 1 or 2 above. So I'd rather be patient and wait for other answers. – ardnew Jul 31 '23 at 07:05
  • @ardnew The objective is a bit far away as I see it. This answer supplies one possible resolution. If I had written an answer, I wouldn't even have tried to give one. I would have settled for the part focusing on that you have misunderstood the language rules and need to redesign - after embracing the rules ofcourse. – Ted Lyngmo Aug 01 '23 at 05:21