Forward declarations for most useful runtime classes of the C++ 17 standard library.
DISCLAIMER: This project is meant as a proof-of-concept for a proposal to standardize a forward declaration header for std. Using it is UB and should only be done to evaluate the proposal (see FAQ at the bottom).
TL;DR: adding #include <stdfwd.hh> adds about 3 ms per translation unit.
| file | clang-6 | clang-7 | gcc-7 | gcc-8 | vs19 |
|---|---|---|---|---|---|
| empty | 12.6 ms | 13.9 ms | 5.5 ms | 5.5 ms | 104.8 ms |
<stdfwd.hh> |
+ 3.0 ms | + 3.3 ms | + 2.8 ms | + 2.2 ms | + 6.7 ms |
<type_traits> |
+ 9.9 ms | + 10.6 ms | + 8.4 ms | + 7.2 ms | + 28.2 ms |
<utility> |
+ 14.1 ms | + 14.5 ms | + 11.6 ms | + 10.2 ms | + 31.0 ms |
<string_view> |
+ 39.4 ms | + 42.1 ms | + 36.4 ms | + 30.9 ms | + 121.6 ms |
<vector> |
+ 44.1 ms | + 47.4 ms | + 41.0 ms | + 35.5 ms | + 89.9 ms |
<string> |
+ 93.1 ms | + 99.2 ms | + 90.3 ms | + 79.8 ms | + 137.7 ms |
<array> |
+ 95.9 ms | + 102.4 ms | + 93.8 ms | + 83.1 ms | + 248.2 ms |
<optional> |
+ 100.1 ms | + 107.1 ms | + 97.6 ms | + 87.4 ms | + 85.1 ms |
<map> |
+ 127.2 ms | + 136.0 ms | + 123.3 ms | + 109.6 ms | + 94.9 ms |
<memory> |
+ 127.5 ms | + 135.3 ms | + 124.8 ms | + 109.9 ms | + 89.2 ms |
<iostream> |
+ 137.4 ms | + 144.8 ms | + 139.6 ms | + 121.9 ms | + 238.0 ms |
<unordered_map> |
+ 142.4 ms | + 149.0 ms | + 135.6 ms | + 126.0 ms | + 138.2 ms |
<functional> |
+ 180.1 ms | + 191.3 ms | + 172.7 ms | + 156.8 ms | + 160.1 ms |
<regex> |
+ 297.8 ms | + 312.1 ms | + 303.1 ms | + 262.9 ms | + 332.5 ms |
Just compiling a single source including the specified file (compiler -std=c++17 -O0 -c /tmp/file.cc -o /tmp/file.o), best out of 10 compilations.
(system: intel i9-9900k 5 GHz, samsung 970 1TB pro nvme ssd, linux mint 19.1 tessa, kernel 4.15.0)
Windows benchmark done on Win10 with /GS /TP /W3 /Zc:wchar_t /Gm- /Od /Ob0 /Zc:inline /fp:precise /WX- /Zc:forScope /RTC1 /GR /Gd /MDd /std:c++17 /FC /EHsc /c.
Use forward declarations where possible in the header:
#include <stdfwd.hh>
stdfwd::string get_string();
stdfwd::vector<int> get_vector();
stdfwd::deque<int> get_deque();
stdfwd::list<int> get_list();
stdfwd::stack<int> get_stack();
stdfwd::forward_list<int> get_forward_list();
stdfwd::shared_ptr<int> get_shared_ptr();
stdfwd::unique_ptr<int> get_unique_ptr();
stdfwd::array<int, 3> get_array();
stdfwd::function<int()> get_function();
stdfwd::bitset<3> get_bitset();
stdfwd::pair<int, int> get_pair();
stdfwd::map<int, int> get_map();
stdfwd::set<int> get_set();
stdfwd::unordered_map<int, int> get_unordered_map();
stdfwd::unordered_set<int> get_unordered_set();
...And in the .cc just #include <header> and define the functions using the normal std type (all declarations inside stdfwd are typedefs into std).
std:: forward declarations can also be used but won't have default template arguments (e.g. std::vector<int> does not work and std::vector<int, std::allocator<int>> or stdfwd::vector<int> must be used).
Adding support for map and unordered_map for custom data types:
#include <stdfwd.hh>
struct foo
{
int v;
bool operator==(foo const& r) const { return v == r.v; }
};
template <>
struct std::hash<foo>
{
size_t operator()(foo const& f) const noexcept { return f.v; }
};
template <>
struct std::less<foo>
{
bool operator()(foo const& a, foo const& b) const noexcept { return a.v < b.v; }
};If the CMakeLists.txt is included, an interface library std-fwd is created:
# if set, force-includes "stdfwd.hh" in every translation unit
# this is optional and low-cost (see benchmarks)
# set(STDFWD_FORCE_INCLUDE ON CACHE BOOL "" FORCE)
add_subdirectory(path/to/cpp-std-fwd)
target_link_libraries(${PROJECT_NAME} PUBLIC std-fwd)-
Why?
Each non-trivial
stdheader adds 50-200 ms compile time just by including them.#include <stdfwd.hh>is virtually free. -
Isn't adding declarations to
stdundefined behavior?Yes. This project is meant as a proof-of-concept for a proposal to standardize a forward declaration header for
std. -
Why the namespace
stdfwd?Some classes like
std::vectorhave default template arguments which must only appear on a declaration once. If they appear on the definition then they cannot appear on the forward declaration. Thus, all forward declarations instdare without default arguments andstdfwdadds typedefs with the appropriate defaults. -
Why are some classes like
std::integer_sequencemissing?I omitted classes that are intended for compile-time programming because there seems to be little reason to forward declare them.
-
What are the use cases?
Many functions defined in a header but implemented elsewhere don't require complete definitions of their arguments or return types.
std::vector<int> foo();can be defined without including<vector>given forward declarations.Furthermore, many data types might want to specialize
std::lessorstd::hashso that their users can use them insidestd::maporstd::unordered_map. Currently this requires including<functional>(one of the most expensivestdheaders to include) even if the data type itself uses nothing from that header. With a forward declaration,lessandhashcan be specialized without including the full header. -
Won't this be obsolete with modules?
Yes and no. In C++20,
stditself is not yet modularized. Additionally, it is not clear if all code bases can be immediately migrated to modules. Thisstdforward header is a small and easy-to-implement non-intrusive change with big payoffs for many code bases. -
Why is
iosfwdnot used internally?This header should be as fast as possible.
iosfwdincludes more than forward declaration. It also fully defines thepostypesat least in libstdc++.
- add support for
libc++