The following information is from The C++ Programming Language (4th Edition):
§3.2.1 "The defining characteristic of a concrete type is that its representation is part of its definition"
§16.3 "Such types are called concrete types or concrete classes to distinguish them from abstract
classes (§20.4) and classes in class hierarchies (§20.3, §21.2)"
§16.3 "A class is called concrete (or a concrete class) if its representation is part of its definition. This
distinguishes it from abstract classes (§3.2.2, §20.4) which provide an interface to a variety of
implementations."
§16.3.4: "In
particular, concrete types are not intended to display run-time polymorphic behavior (see §3.2.3, §20.3.2)" [§3.2.3 and §20.3.2 have the title "Virtual Functions"]
§16.3.4: "Alternatively, the derived class mechanism discussed in Chapter 20 can be used to define new types
from a concrete class by describing the desired differences. The definition of Vec from vector
(§4.4.1.2) is an example of this. However, derivation from a concrete class should be done with
care and only rarely because of the lack of virtual functions and run-time type information
(§17.5.1.4, Chapter 22)."
Bjarne Stroustrup sees concrete types as synonymous to value types.
With value-oriented-programming differing from certain aspects of object-oriented-programming.
Concrete types show no run-time polymorphic behaviour. So the means to modify the behaviour of concrete types with inheritance is limited. It is allowed that a concrete type aggregates other types as member variables. A concrete type can also inherit from other concrete types. It is also allowed that a concrete type uses the free store (heap) for storing data and access it with pointers.
The deciding property seems to be, concrete types do not have any virtual functions. The opposite types - those with virtual members - are often called polymorphic types (or reference types according to the Microsoft C++ documentation, some other experts define reference types differently), so a concrete type is non-polymorphic.
Abstract types are always polymorphic types, but the reverse is not generally true.
Concrete types are typically (semi- or) regular types, offering the default operations in the Rule of Six.
(However, cppreference defines all instantiable classes (non-abstract classes) as concrete, and concrete classes can inherit from (or be the base of) abstract classes: https://en.cppreference.com/w/cpp/language/abstract_class)
I am not sure, whether BS would see all 'resource types' (e.g. network sockets, files, ..., convered by RAII) as concrete types, even if they are not polymorphic. The resource types often cannot be copied and sometimes need special initialization (non-default constructors or something even more intricate).
I am also not sure, if BS would see final classes with virtual functions or classes with virtual functions that have all been finalized (called devirtualization) as concrete: If you have a variable of this type, you know, which functions are called (the ones defined in the class) - those types no longer show run-time polymorphic behaviour.
Perhaps we should - for the sake of this discussion - distinguish between the actually instantiated class type (dynamic type) and the type, with which we access that class in program code (static type). The properties of concrete classes, as described by BS, can only be realized, if the class type is accessed by its actual type (and not by the type of one of its base classes). So usage patterns matter.
struct variable_length { char data[]; }.