A customer was adding a feature to an old C++ code base, and the most convenient way to consume the feature was to use C++/WinRT. However, once they added C++/WinRT to their project, they ran into compiler errors in parts of their code that hadn’t changed in decades. As an added wrinkle, the problem occurred only in 32-bit builds.
std::ostream& operator<<(ostream& os, LARGE_INTEGER const& value)
{
return os << value.QuadPart; // ← error
}
The error complained that the << operator was ambiguous.
contoso.cpp(3141) : error C2593: 'operator <<' is ambiguous ostream(436): note: could be 'std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(long double)' ostream(418): note: or 'std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(double)' ostream(400): note: or 'std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(float)' ostream(382): note: or 'std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(unsigned __int64)' ostream(364): note: or 'std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(__int64)' ostream(346): note: or 'std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(unsigned long)' ostream(328): note: or 'std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(long)' ostream(309): note: or 'std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(unsigned int)' ostream(283): note: or 'std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(int)' ostream(264): note: or 'std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(unsigned short)' ostream(230): note: or 'std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(short)' ostream(212): note: or 'std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(bool)' contoso.h(1554): note: or 'std::ostream &operator <<(std::ostream &,const unsigned __int64 &)' contoso.h(1548): note: or 'std::ostream &operator <<(std::ostream &,const __int64 &)' ostream(953): note: or 'std::basic_ostream<char,std::char_traits<char>> &std::operator <<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,unsigned char)' ostream(942): note: or 'std::basic_ostream<char,std::char_traits<char>> &std::operator <<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,signed char)' ostream(819): note: or 'std::basic_ostream<char,std::char_traits<char>> &std::operator <<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,char)' ostream(738): note: or 'std::basic_ostream<char,std::char_traits<char>> &std::operator <<<char,std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,char)' contoso.cpp(1582): note: while trying to match the argument list '(std::ostream, LONGLONG)'
All of these are different overloads of std::ostream& std::ostream::operator<<(something) or std::ostream& operator<<(std::ostream&, something), so let’s remove all the repeated stuff to make it easier to see what we are up against.
ostream(436): long double ostream(418): double ostream(400): float ostream(382): unsigned __int64 ostream(364): __int64 ostream(346): unsigned long ostream(328): long ostream(309): unsigned int ostream(283): int ostream(264): unsigned short ostream(230): short ostream(212): bool contoso.h(1554): const unsigned __int64 & contoso.h(1548): const __int64 & ostream(953): unsigned char ostream(942): signed char ostream(819): char ostream(738): char
From the code, we see that the intention is to use the insertion operator that takes a signed 64-bit integer, so let’s filter down to those.
ostream(364): __int64 contoso.h(1548): const __int64 &
Aha, now we see the conflict. The C++ standard library (<ostream>) has defined an output inserter for __int64, and the customer has defined an output inserter for const __int64&, so the compiler can’t choose between them.
The compiler kindly provided line numbers, so we can look at the conflict introduced by contoso.h.
#if !defined(_WIN64) && !defined(_STL70_) && !defined(_STL110_) // These are already defined in STL std::ostream& operator<<(std::ostream&, const __int64& ); std::ostream& operator<<(ostd::stream&, const unsigned __int64& ); #endif /* _WIN64 _STL70_ _STL110_ */
Okay, well the !defined(_WIN64) explains why this problem occurs only in 32-bit builds: The conflicting definition is #if‘d out in 64-bit builds.
The rest of the #if expression removes the conflicting definition for STL versions 7.0 and 11.0. So what happened to reactivate this code path?
Adding C++/WinRT to the project.
C++/WinRT requires C++17 or later, which means that the project had to bump its compiler version, and that pushed the STL version to 12.0. And their custom #if doesn’t handle that case.
I went back through the project history, and saw that about five years ago, the line was just
#if !defined(_WIN64) && !defined(_STL70_)
So I’m guessing that at some point in the past five years, they upgraded their compiler version, and they ran into this exact problem, and they realized, “Oh, we need to suppress this for STL 11.0, too,” and they added the !defined(_STL110_).
History repeats itself.
One solution is to put another layer of duct tape on it.
#if !defined(_WIN64) && !defined(_STL70_) && !defined(_STL110_) && !defined(_STL120_)
Of course, this just means that in another five years, when they decide to upgrade to C++30, this problem will come back and somebody will have to add yet another layer of duct tape.
So they could choose something that is a bit more forward-compatible:
#if !defined(_WIN64) && !defined(_STL70_) && !defined(_STL110_) && __cplusplus < 201700
Or they could just delete the entire block. I doubt they are going to roll their compiler back to C++03.
0 comments
Be the first to start the discussion.