首页 > 解决方案 > 转换运算符重载:gcc 与 clang 问题

问题描述

我正在尝试编写一个基本的 std::any 替代方案以在我的代码中使用,原因是我想用转换运算符替换模板化的 std::any_cast 。

用例是向结构添加一些自省/反射,最后能够向结构询问其暴露的成员并按名称访问它们(如 std::map 中的键)。

向结构添加的功能应该是潜在的虚拟,模板类将不起作用。

代码实际上是这样的:

   // "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 的其他方法.

所以我的问题:

  1. 这是正常行为还是 Gcc 缺陷???
  2. 如果这是正常的,我怎样才能让 Gcc 得到 wright 过载?

标签: c++templatesconversion-operator

解决方案


您的示例可以简化为:

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 给出

错误:无法在初始化中将“S”转换为“int”

演示

并根据Non-class_initialization_by_conversion

S 及其基类(除非隐藏)的非显式用户定义转换函数产生类型 T 或可通过标准转换序列转换为 T 的类型,或对此类类型的引用。出于选择候选函数的目的,忽略返回类型上的 cv 限定符。

据我了解,只有运营商intorint&应该被考虑。

所以 gcc 是对的,但在非模板版本Demo上两者都是可行的......


推荐阅读