The code
Here's this seemingly innocent, few lines of code.
#include <iostream>
#include <string>
#include <unordered_map>
struct Tag
{
using name_t = std::string;
using tag_map_t = std::unordered_map<name_t, Tag>;
name_t name;
tag_map_t children = {};
};
int main(void)
{
auto foo = Tag{"foo"};
auto bar = Tag{"bar"};
foo.children["bar"] = bar;
std::cout << foo.name << " -> " << foo.children["bar"].name << std::endl;
return 0;
}
Let's try to compile it
clang
λ clang++ --version
clang version 11.0.1
Target: x86_64-pc-windows-msvc
Thread model: posix
InstalledDir: C:\LLVM\bin
λ clang++ -std=c++14 -Weverything -Werror -Wno-c++98-compat tag.cpp -o tag.exe && tag.exe
foo -> bar
msvc++
λ cl
Microsoft (R) C/C++ Optimizing Compiler Version 19.16.27031.1 for x64
Copyright (C) Microsoft Corporation. All rights reserved.
λ vcvars64.bat && cl.exe /std:c++14 /W4 /WX /EHsc tag.cpp /Fe:tag.exe && tag.exe
**********************************************************************
** Visual Studio 2017 Developer Command Prompt v15.9.13
** Copyright (c) 2017 Microsoft Corporation
**********************************************************************
[vcvarsall.bat] Environment initialized for: 'x64'
Microsoft (R) C/C++ Optimizing Compiler Version 19.16.27031.1 for x64
Copyright (C) Microsoft Corporation. All rights reserved.
tag.cpp
Microsoft (R) Incremental Linker Version 14.16.27031.1
Copyright (C) Microsoft Corporation. All rights reserved.
/out:tag.exe
tag.obj
foo -> bar
gcc
λ gcc --version
gcc (GCC) 10.2.0
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
λ g++ -std=c++14 -pedantic -Wall tag.cpp -o tag.exe && tag.exe
In file included from /usr/lib/gcc/x86_64-pc-msys/10.2.0/include/c++/bits/stl_algobase.h:64,
from /usr/lib/gcc/x86_64-pc-msys/10.2.0/include/c++/bits/char_traits.h:39,
from /usr/lib/gcc/x86_64-pc-msys/10.2.0/include/c++/ios:40,
from /usr/lib/gcc/x86_64-pc-msys/10.2.0/include/c++/ostream:38,
from /usr/lib/gcc/x86_64-pc-msys/10.2.0/include/c++/iostream:39,
from tag.cpp:1:
/usr/lib/gcc/x86_64-pc-msys/10.2.0/include/c++/bits/stl_pair.h: In instantiation of ‘struct std::pair<const std::basic_string<char>, Tag>’:
/usr/lib/gcc/x86_64-pc-msys/10.2.0/include/c++/ext/aligned_buffer.h:91:28: required from ‘struct __gnu_cxx::__aligned_buffer<std::pair<const std::basic_string<char>, Tag> >’
/usr/lib/gcc/x86_64-pc-msys/10.2.0/include/c++/bits/hashtable_policy.h:233:43: required from ‘struct std::__detail::_Hash_node_value_base<std::pair<const std::basic_string<char>, Tag> >’
/usr/lib/gcc/x86_64-pc-msys/10.2.0/include/c++/bits/hashtable_policy.h:264:12: required from ‘struct std::__detail::_Hash_node<std::pair<const std::basic_string<char>, Tag>, true>’
/usr/lib/gcc/x86_64-pc-msys/10.2.0/include/c++/bits/hashtable_policy.h:1973:13: required from ‘struct std::__detail::_Hashtable_alloc<std::allocator<std::__detail::_Hash_node<std::pair<const std::basic_string<char>, Tag>, true> > >’/usr/lib/gcc/x86_64-pc-msys/10.2.0/include/c++/bits/hashtable.h:173:11: required from ‘class std::_Hashtable<std::basic_string<char>, std::pair<const std::basic_string<char>, Tag>, std::allocator<std::pair<const std::basic_string<char>, Tag> >, std::__detail::_Select1st, std::equal_to<std::basic_string<char> >, std::hash<std::basic_string<char> >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >’
/usr/lib/gcc/x86_64-pc-msys/10.2.0/include/c++/bits/unordered_map.h:105:18: required from ‘class std::unordered_map<std::basic_string<char>, Tag>’
tag.cpp:11:37: required from here
/usr/lib/gcc/x86_64-pc-msys/10.2.0/include/c++/bits/stl_pair.h:218:11: error: ‘std::pair<_T1, _T2>::second’ has incomplete type
218 | _T2 second; ///< The second member
| ^~~~~~
tag.cpp:5:8: note: forward declaration of ‘struct Tag’
5 | struct Tag
| ^~~
I'm pretty sure gcc
is right and I don't understand how clang
and visual studio
can compile this and turn it into a binary that can even run.
How do they know the size of Tag
? Tag
is a recursive data structure, right? So somehow these compilers can figure out the size of Tag
even though I haven't stored like a shared_ptr
in the unordered_map
, but the Tag
itself. Is this valid C++ code?
Why I haven't even needed a forward declaration for the struct
? I thought in C++ the compiler needs to "see" at least a declaration for a name to take it as a valid name, so I'm surprised this does not seem to be the case here. At least according to clang
and visual studio
. What important detail did I miss here?
Fixing this code to make it work on gcc
is really simple, so my question is really about how it can work with the other two compilers.