Initialization of inner private class within public constructor call of outer class -What’s the standard’s intention for this?

Recently, I came across code like this:

class NeedsFactoryForPublicCreation
{
private:
    struct Accessor
    {
      // Enable in order to make the compile failing (expected behavior)
      // explicit Accessor() = default;
    };
 
public:
    explicit NeedsFactoryForPublicCreation(Accessor) {}
    
    // This was intended to be the only public construction way
    static NeedsFactoryForPublicCreation create()
    {
        NeedsFactoryForPublicCreation result{Accessor()};
        // ... Do something (complex parametrization) further on
        return result;
    }
};

int main()
{
    //NeedsFactoryForPublicCreation::Accessor publicAccessor {}; // Does not compile as expected
    NeedsFactoryForPublicCreation nonDefaultConstructible {{}}; // Compiles even with Accessor being an interal
}

At first, I was a bit shocked, that this compiles.
After some minutes of analysis (and occurring self-doubts…), I found out, that this is fully explainable due to the definition of access specifiers and the way the compiler decides what actual way of initialization is used (aggregate vs. public constructor look-up). In order to extend the first confusion, even this access class compiles this way:

class Accessor
{
    Accessor() = default; // private, but we are an aggregate...
    friend class NeedsFactoryForPublicCreation;
};

which is again fully explainable since Accessor is still an aggregate (another confusing topic…).

But in order to emphasize, that this question has nothing to do with aggregates in detail (thanks to Jarod42 in the comments to point out the upcoming C++20 changes here!), this compiles too:

class Accessor
{
    public:
    Accessor() {}
    virtual ~Accessor() {}
    virtual void enSureNonAggregate() const {}
    friend class NeedsFactoryForPublicCreation;
};

and does not as soon as the constructor becomes private.

My question: What’s the actual background, the standard decided the effect of access specifiers this counterintuitively in doubt? With counterintuitively I especially mean the inconsistency of effective look-up (the compiler doesn’t care about the internal as long as we stay unnamed, but then cares when it comes to actual constructor look-up, still fully unnamed context). Or maybe I’m mixing categories too much here.

I know, access specifiers’ background is quite strictly naming based and there are also other ways to achieve the "publication" of this internal, but as far as I know, they are far more explicit. Legally implicitly creating an unnamed temporary of a private internal within an outer scope via this extremely implicit way looks horrible and might even be quite error prone, at latest when multiple arguments are the case (uniform initialized empty std-containers…).

Source: Windows Questions C++

LEAVE A COMMENT