In C++14, the definition of aggregate was:
An aggregate is an array or a class (Clause [class]) with no user-provided constructors ([class.ctor]), no private or protected non-static data members (Clause [class.access]), no base classes (Clause [class.derived]), and no virtual functions ([class.virtual]).
Hence, B is not an aggregate. As a result B{} is surely not aggregate initialization, and B{} and B() end up meaning the same thing. They both just invoke B's default constructor.
However, in C++17, the definition of aggregate was changed to:
An aggregate is an array or a class with
- no user-provided, explicit, or inherited constructors ([class.ctor]),
- no private or protected non-static data members (Clause [class.access]),
- no virtual functions, and
- no virtual, private, or protected base classes ([class.mi]).
[ Note: Aggregate initialization does not allow accessing protected and private base class' members or constructors. — end note ]
The restriction is no longer on any base classes, but just on virtual/private/protected ones. But B has a public base class. It is now an aggregate! And C++17 aggregate initialization does allow for initializing base class subobjects.
In particular, B{} is aggregate initialization where we just don't provide an initializer for any subobject. But the first (and only) subobject is an A, which we're trying to initialize from {} (during aggregate initialization, any subobject without an explicit initializer is copy-initialized from {}), which we can't do because A's constructor is protected and we are not a friend (see also, the quoted note).
Note that, just for fun, in C++20 the definition of aggregate will change again.