我有以下代码,可以在 GCC 13.2 上正常编译。同事在GCC 13.1上无法编译。即使使用
-Wall -Wextra
,新版本也能正常编译。我认为 GCC 13.2 更好、更正确。哪些变化使这成为可能?为什么之前被认为有歧义的这个调用却没有歧义?
上帝闪电:https://godbolt.org/z/e9qKa4eza
该代码允许转换为
T*
,在 char*
的情况下,允许在不使用 operator std::string
的情况下构造字符串。因此,StringifiedData<char>
有效地绕过了通过 StringStream 的字符串创建:
#include <string>
#include <sstream>
#include <algorithm>
template <typename T>
class StringifiedData
{
private:
size_t m_size{};
char * m_data{nullptr};
public:
StringifiedData(char * buffer)
: m_size{*reinterpret_cast<size_t*>(buffer)}, m_data{buffer + sizeof(size_t)}
{}
~StringifiedData() = default;
size_t size() const { return m_size; }
T * begin() const { return reinterpret_cast<T *>(m_data); }
T * end() const { return begin() + m_size; }
operator T *() const { return begin(); }
explicit operator std::string() const
{
std::stringstream sstr;
sstr << m_size;
std::for_each(begin(), end(), [&sstr](auto data){ sstr << "," << data; });
return sstr.str();
}
};
#include <iostream>
int main()
{
char buffer[128];
*reinterpret_cast<size_t*>(buffer) = 3;
StringifiedData<char> uCData(buffer);
uCData[0] = 'a';
uCData[1] = 'b';
uCData[2] = 'c';
std::cout << uCData.size() << ',' << uCData[0] << ',' << uCData[1] << ',' << uCData[2] << std::endl;
std::cout << static_cast<std::string>(uCData) << "\n" << std::endl;
}
GCC 13.1 的错误消息:
<source>:41:49: error: call of overloaded 'basic_string(StringifiedData<char>&)' is ambiguous
41 | std::cout << static_cast<std::string>(uCData) << "\n" << std::endl;
| ^
In file included from /opt/compiler-explorer/gcc-13.1.0/include/c++/13.1.0/string:54,
from <source>:1:
/opt/compiler-explorer/gcc-13.1.0/include/c++/13.1.0/bits/basic_string.h:678:7: note: candidate: 'std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::basic_string(std::__cxx11::basic_string<_CharT, _Traits, _Alloc>&&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]'
678 | basic_string(basic_string&& __str) noexcept
| ^~~~~~~~~~~~
/opt/compiler-explorer/gcc-13.1.0/include/c++/13.1.0/bits/basic_string.h:642:7: note: candidate: 'std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::basic_string(const _CharT*, const _Alloc&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]'
642 | basic_string(const _CharT* __s, const _Alloc& __a = _Alloc())
| ^~~~~~~~~~~~
/opt/compiler-explorer/gcc-13.1.0/include/c++/13.1.0/bits/basic_string.h:548:7: note: candidate: 'std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::basic_string(const std::__cxx11::basic_string<_CharT, _Traits, _Alloc>&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]'
548 | basic_string(const basic_string& __str)
| ^~~~~~~~~~~~
Compiler returned: 1
其他注意事项:
您正在使用
-std=c++14
进行编译。编译器中旧语言模式的模拟只是近似的,特别是因为新的 DR 不断针对旧标准模式被发现。
GCC 13.2 中影响此代码行为的更改似乎是 9872d56,它对应于 CWG2735 的拟议解决方案。 在 GCC 13.1 中,您会看到构造函数
string(string&&)
和 string(const char*)
之间存在歧义,因为第一个构造函数通过显式 operator std::string
可行,第二个通过隐式 operator T*
可行。上述提交中的调整不利于 string(string&&)
,因为它是一个移动构造函数,它使用显式的用户定义转换来初始化其参数。但我认为该提交超出了意图,因为它只应该影响列表初始化,并且仅在模糊性涉及候选者的情况下,如果无论如何选择,该候选者将导致格式错误的程序。在您的代码中,情况有所不同,因为我们处于实际上允许隐式转换序列调用显式函数的上下文中。请参阅 [over.match.copy]/1.2 中的直接初始化特殊规则。所以可以说这是 GCC 13.2 及更高版本中的一个错误。
请注意,在 C++17 及更高版本中,GCC 实现了对 CWG2327 的可能解决方案,这使得
string(string&&)
成为最佳构造函数,因为它被视为用户直接调用 operator std::string
,而不是需要用户调用 string(const char*)
-定义的转换(调用operator T*
)来初始化其参数。