转换运算符重载:gcc vs clang 问题
conversion operator overload : gcc vs clang problem
我正在尝试编写一个基本的 std::any 替代方案以在我的代码中使用,原因是我想用转换运算符替换模板化的 std::any_cast。
用例是向结构中添加一些 introspection/reflection,并最终能够为其公开的成员请求结构并通过名称访问它们(与 std::map 中的键一样)。
添加到结构中的函数应该是潜在的虚拟的,模板化的 class 将不起作用。
代码实际上是这样的:
// "std::any" like type that hold a "reference" to any type using a void*
// -> keep track of original type using "std::type_info"
// -> provide a "conversion operator" instead of "std::any_cast<T>"
// -> default initialized aRef object will only fail with std::bad_cast{}
struct aRef {
constexpr aRef() : ptr(nullptr), is_const(false), type(typeid(void)) { }
template<typename T> explicit aRef(T* in) : ptr(in), is_const(false), type(typeid(T)) { }
template<typename T> explicit aRef(const T* in) : ptr(const_cast<T*>(in)), is_const(true), type(typeid(T)) { }
template<typename T> operator T&() {
std::cout<<"operator T&() ";
if (is_const) std::cout<<"----> make compiler issue an error !!! "<<std::endl;
else std::cout<<std::endl;
if (typeid(T) == type) return *( static_cast<T*>(ptr) );
else throw std::bad_cast{};
}
template<typename T> operator const T&(){
std::cout<<"operator const T&() "<<std::endl;
if (typeid(T) == type) return *( static_cast<T*>(ptr) );
else throw std::bad_cast{};
}
private :
void* const ptr;
const bool is_const;
std::type_info const& type;
};
// what it should achieve :
// T& = aRef( T*) --> ok
// T& = aRef(const T*) --> not ok, break constness
// T = aRef( T*) --> ok
// T = aRef(const T*) --> ok
// const T& = aRef( T*) --> ok
// const T& = aRef(const T*) --> ok
// the structure to instrument
struct MDATA {
double A;
double B;
// instrumentation code , functions can be polymorphic
// functions only -> zero overhead in size
aRef operator[](const std::string& key) {
if (key == "A") return aRef(&A);
else if (key == "B") return aRef(&B);
else return aRef();
}
aRef operator[](const std::string& key) const {
if (key == "A") return aRef(&A);
else if (key == "B") return aRef(&B);
else return aRef();
}
};
// dummy function
void do_something(const double& x) { }
int main () {
MDATA data {0.2,1.2};
// T& = aRef( T*) --> ok
{
std::cout<<" T& = aRef( T*)... ";
double& xx = data["A"];
xx = 125.0; do_something(xx);
}
// T = aRef( T*) --> ok
{
std::cout<<" T = aRef( T*)... ";
double xx = data["B"];
xx = -521.0; do_something(xx); // local !!
}
// const T& = aRef( T*) --> ok
{
std::cout<<" const T& = aRef( T*)... ";
const double& xx = data["A"];
do_something(xx);
}
const MDATA& cdata = data;
// T& = aRef( const T*) --> not ok
{
std::cout<<" T& = aRef( const T*)... ";
double& xx = cdata["A"];
xx = 125.0; do_something(xx);
}
// T = aRef(const T*) --> ok
{
std::cout<<" T = aRef( const T*)... ";
double xx = cdata["B"];
xx = -521.0; do_something(xx); // local !!
}
// const T& = aRef(const T*) --> ok
{
std::cout<<" const T& = aRef( const T*)... ";
const double& xx = cdata["A"];
do_something(xx);
}
return 0;
}
现在这是 gcc 11.1 生成的结果:
| | |
| --------------------------- | ----------------------------------------------------------- |
| T& = aRef( T*) | operator T&() |
| T = aRef( T*) | operator T&() |
| const T& = aRef( T*) | operator const T&() |
| T& = aRef( const T*) | operator T&() ----> make compiler issue an error !!! |
| T = aRef( const T*) | operator T&() ----> make compiler issue an error !!! |
| const T& = aRef( const T*) | operator const T&() |
和由 clang 11.1 生成的那个
| | |
| --------------------------- | ----------------------------------------------------------- |
| T& = aRef( T*) | operator T&() |
| T = aRef( T*) | operator const T&() |
| const T& = aRef( T*) | operator const T&() |
| T& = aRef( const T*) | operator T&() ----> make compiler issue an error !!! |
| T = aRef( const T*) | operator const T&() |
| const T& = aRef( const T*) | operator const T&() |
如您所见,Clang 给出了所需的行为而不是 Gcc,问题是在分配给值( T = aRef(.) )时调用转换运算符,这是 clang 的 const 方法(对我来说似乎合乎逻辑) 和 Gcc 的其他。
所以我的问题是:
- 这是正常行为还是 Gcc 缺陷???
- 如果这是正常的,我怎样才能让 Gcc 获得 wright 重载??
您的示例可以简化为:
struct S
{
int n1 = 1;
const int n2 = 2;
//template<typename T> operator T&() { return n1; }
template<typename T> operator const T&() { return n2; }
};
int main()
{
S s;
int n = s;
std::cout << n << std::endl;
}
gcc 给出
error: cannot convert 'S' to 'int' in initialization
并根据Non-class_initialization_by_conversion
the non-explicit user-defined conversion functions of S and its base classes (unless hidden) that produce type T or a type convertible to T by a standard conversion sequence, or a reference to such type. cv qualifiers on the returned type are ignored for the purpose of selecting candidate functions.
据我了解,只应考虑运算符 int
或 int&
。
所以 gcc 是正确的,但在非模板版本上 Demo 两者都是可行的...
我正在尝试编写一个基本的 std::any 替代方案以在我的代码中使用,原因是我想用转换运算符替换模板化的 std::any_cast。
用例是向结构中添加一些 introspection/reflection,并最终能够为其公开的成员请求结构并通过名称访问它们(与 std::map 中的键一样)。
添加到结构中的函数应该是潜在的虚拟的,模板化的 class 将不起作用。
代码实际上是这样的:
// "std::any" like type that hold a "reference" to any type using a void*
// -> keep track of original type using "std::type_info"
// -> provide a "conversion operator" instead of "std::any_cast<T>"
// -> default initialized aRef object will only fail with std::bad_cast{}
struct aRef {
constexpr aRef() : ptr(nullptr), is_const(false), type(typeid(void)) { }
template<typename T> explicit aRef(T* in) : ptr(in), is_const(false), type(typeid(T)) { }
template<typename T> explicit aRef(const T* in) : ptr(const_cast<T*>(in)), is_const(true), type(typeid(T)) { }
template<typename T> operator T&() {
std::cout<<"operator T&() ";
if (is_const) std::cout<<"----> make compiler issue an error !!! "<<std::endl;
else std::cout<<std::endl;
if (typeid(T) == type) return *( static_cast<T*>(ptr) );
else throw std::bad_cast{};
}
template<typename T> operator const T&(){
std::cout<<"operator const T&() "<<std::endl;
if (typeid(T) == type) return *( static_cast<T*>(ptr) );
else throw std::bad_cast{};
}
private :
void* const ptr;
const bool is_const;
std::type_info const& type;
};
// what it should achieve :
// T& = aRef( T*) --> ok
// T& = aRef(const T*) --> not ok, break constness
// T = aRef( T*) --> ok
// T = aRef(const T*) --> ok
// const T& = aRef( T*) --> ok
// const T& = aRef(const T*) --> ok
// the structure to instrument
struct MDATA {
double A;
double B;
// instrumentation code , functions can be polymorphic
// functions only -> zero overhead in size
aRef operator[](const std::string& key) {
if (key == "A") return aRef(&A);
else if (key == "B") return aRef(&B);
else return aRef();
}
aRef operator[](const std::string& key) const {
if (key == "A") return aRef(&A);
else if (key == "B") return aRef(&B);
else return aRef();
}
};
// dummy function
void do_something(const double& x) { }
int main () {
MDATA data {0.2,1.2};
// T& = aRef( T*) --> ok
{
std::cout<<" T& = aRef( T*)... ";
double& xx = data["A"];
xx = 125.0; do_something(xx);
}
// T = aRef( T*) --> ok
{
std::cout<<" T = aRef( T*)... ";
double xx = data["B"];
xx = -521.0; do_something(xx); // local !!
}
// const T& = aRef( T*) --> ok
{
std::cout<<" const T& = aRef( T*)... ";
const double& xx = data["A"];
do_something(xx);
}
const MDATA& cdata = data;
// T& = aRef( const T*) --> not ok
{
std::cout<<" T& = aRef( const T*)... ";
double& xx = cdata["A"];
xx = 125.0; do_something(xx);
}
// T = aRef(const T*) --> ok
{
std::cout<<" T = aRef( const T*)... ";
double xx = cdata["B"];
xx = -521.0; do_something(xx); // local !!
}
// const T& = aRef(const T*) --> ok
{
std::cout<<" const T& = aRef( const T*)... ";
const double& xx = cdata["A"];
do_something(xx);
}
return 0;
}
现在这是 gcc 11.1 生成的结果:
| | |
| --------------------------- | ----------------------------------------------------------- |
| T& = aRef( T*) | operator T&() |
| T = aRef( T*) | operator T&() |
| const T& = aRef( T*) | operator const T&() |
| T& = aRef( const T*) | operator T&() ----> make compiler issue an error !!! |
| T = aRef( const T*) | operator T&() ----> make compiler issue an error !!! |
| const T& = aRef( const T*) | operator const T&() |
和由 clang 11.1 生成的那个
| | |
| --------------------------- | ----------------------------------------------------------- |
| T& = aRef( T*) | operator T&() |
| T = aRef( T*) | operator const T&() |
| const T& = aRef( T*) | operator const T&() |
| T& = aRef( const T*) | operator T&() ----> make compiler issue an error !!! |
| T = aRef( const T*) | operator const T&() |
| const T& = aRef( const T*) | operator const T&() |
如您所见,Clang 给出了所需的行为而不是 Gcc,问题是在分配给值( T = aRef(.) )时调用转换运算符,这是 clang 的 const 方法(对我来说似乎合乎逻辑) 和 Gcc 的其他。
所以我的问题是:
- 这是正常行为还是 Gcc 缺陷???
- 如果这是正常的,我怎样才能让 Gcc 获得 wright 重载??
您的示例可以简化为:
struct S
{
int n1 = 1;
const int n2 = 2;
//template<typename T> operator T&() { return n1; }
template<typename T> operator const T&() { return n2; }
};
int main()
{
S s;
int n = s;
std::cout << n << std::endl;
}
gcc 给出
error: cannot convert 'S' to 'int' in initialization
并根据Non-class_initialization_by_conversion
the non-explicit user-defined conversion functions of S and its base classes (unless hidden) that produce type T or a type convertible to T by a standard conversion sequence, or a reference to such type. cv qualifiers on the returned type are ignored for the purpose of selecting candidate functions.
据我了解,只应考虑运算符 int
或 int&
。
所以 gcc 是正确的,但在非模板版本上 Demo 两者都是可行的...