June 19th, 2025
0 reactions

Learning to read C++ compiler errors: Ambiguous symbol errors after including a header file

A colleague added another header file to their project, and everything started blowing up with “ambiguous symbol” errors.

#include "pch.h"
#include "something.h"
#include "newheader.h"
#include <shlobj.h>

...

This resulted in a build error in Visual Studio:

D:\Program Files (x86)\Windows Kits\10\Include\10.0.20348.0\um\shlobj_core.h(236,1): error C2872: 'IUnknown': ambiguous symbol
(compiling source file 'Widget.cpp')
    D:\Program Files (x86)\Windows Kits\10\Include\10.0.20348.0\um\unknwnbase.h(117,9):
        could be 'IUnknown'
    D:\Program Files (x86)\Windows Kits\10\Include\10.0.20348.0\cppwinrt\winrt\base.h(312,12):
    or       'winrt::Windows::Foundation::IUnknown'

(repeat a gazillion more times)

The compiler says that the problem is with shlobj_core.h, but really, that’s just where the problem was discovered.

The offending line in shlobj_core.h is

DECLARE_INTERFACE_IID_(IExtractIconA, IUnknown, "000214eb-0000-0000-c000-000000000046")

The DECLARE_INTERFACE_IID_ macro expands to

struct __declspec(uuid(000214eb-0000-0000-c000-000000000046"))
      __declspec(novtable) IExtractIconA : public IUnknown

The compiler reports a problem with the name IUnknown that is being used as the base class because it is ambiguous. It could refer to IUnknown (in the global namespace) or winrt::Windows::Foundation::IUnknown.

But wait, how could IUnknown, when referenced from the global namespace, end up referring to a name in namespace scope?

Answer: If the name has been imported into the global namespace via a using directive.¹

After some searching, they found a header file that contained the line

using namespace winrt::Windows::Foundation;

This imports all of winrt::Windows::Foundation into the global namespace, and that’s creating the name collision.

Putting using namespace directives in header files goes against SF.7: Don’t write using namespace at global scope in a header file.

Instead, qualify the names in the header file. Yes, this makes things wordy, but it’s better than polluting the global namespace.

Importing names into the global namespace should either be scoped or performed from the main C++ file rather than any headers.

¹ Another possible answer is “If the namespace has been added to the search via argument-dependent lookup.” However, this particular usage is not as a function call, so argument-dependent lookup does not apply, seeing as there are no arguments.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

0 comments