【问题标题】:Preventing implicit conversion operator only for binary operators仅针对二元运算符防止隐式转换运算符
【发布时间】:2020-12-27 16:46:17
【问题描述】:

我遇到了一个问题,我已归结为以下问题,其中 == 运算符使用编译即使它应该失败(C++17,在 GCC 5.x、8.x、和 9.x):

template <int N> struct thing {
  operator const char * () const { return nullptr; }
  bool operator == (const thing<N> &) const { return false; }
};

int main () {
  thing<0> a;
  thing<1> b;
  a == b; // i don't want this to compile, but it does
}

它正在编译的原因是因为编译器选择这样做:

(const char *)a == (const char *)b;

而不是这个:

// bool thing<0>::operator == (const thing<0> &) const
a.operator == (b);

因为后者不匹配,因为 bthing&lt;1&gt; 而不是 thing&lt;0&gt;。 (顺便说一句,当使用原始比较运算符时,它还会产生unused-comparison 警告;这并不有趣,但这就是出现这些警告的原因。)

我已经在C++ Insights 上验证了这一点(实际上是我发现原因的方式),它输出:

template <int N> struct thing {
  operator const char * () const { return nullptr; }
  bool operator == (const thing<N> &) const { return false; }
};

/* First instantiated from: insights.cpp:7 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct thing<0>
{
  using retType_2_7 = const char *;
  inline operator retType_2_7 () const
  {
    return nullptr;
  }
  
  inline bool operator==(const thing<0> &) const;
  
  // inline constexpr thing() noexcept = default;
};

#endif


/* First instantiated from: insights.cpp:8 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct thing<1>
{
  using retType_2_7 = const char *;
  inline operator retType_2_7 () const
  {
    return nullptr;
  }
  
  inline bool operator==(const thing<1> &) const;
  
  // inline constexpr thing() noexcept = default;
};

#endif


int main()
{
  thing<0> a = thing<0>();
  thing<1> b = thing<1>();
  static_cast<const char *>(a.operator const char *()) == static_cast<const char *>(b.operator const char *());
}    

main 的最后一行显示转换运算符的用法。

我的问题是:当然,如果我将转换运算符设为显式,整个事情都会正常运行,但是我真的很想保留隐式转换运算符并且让编译器强制执行正确使用operator ==(其中“正确”= 如果参数类型不同则编译失败)。这可能吗?

请注意,让thing&lt;N&gt; == const char * 工作对我来说很重要。也就是说,我不需要这个重载:

bool thing<N>::operator == (const char *) const

所以我不在乎解决方案是否会打破 == 的风格。

我确实在这里搜索过其他帖子;有一些具有误导性相似标题的数字最终是无关的。看起来this post 有一个相关 问题(不需要的隐式转换),但它仍然不适用。


为了完整起见,这里有一个稍微不那么简单但更具代表性的例子,说明我正在做的事情,其目的是让==thing&lt;T,N&gt;thing&lt;R,N&gt; 工作,即@987654340 @ 必须相同,但第一个模板参数可以不同。我将其包括在内以防它影响可能的解决方案,因为这是我真正需要的正确行为:

template <typename T, int N> struct thing {
  operator const char * () const { return nullptr; }
  template <typename R> bool operator == (const thing<R,N> &) const { return false; }
};

int main () {
  thing<int,0> i0;
  thing<float,0> f0;
  thing<int,1> i1;
  i0 == f0;
  f0 == i0;
  i0 == i1; // should fail to compile
  f0 == i1; // should fail to compile
  i1 == i0; // should fail to compile
  i1 == f0; // should fail to compile
}

【问题讨论】:

    标签: c++ templates operator-overloading c++17 implicit-conversion


    【解决方案1】:

    您可以只提供一个deleted 版本的运算符,用于不应该工作的情况,例如:

    template <int N> struct thing {
      operator const void * () const { return nullptr; }
      bool operator == (const thing<N> &) const { return false; }
      template <int X>
      bool operator == (const thing<X> &) const = delete;
    };
    
    int main () {
      thing<0> a;
      thing<1> b;
      a == a; // This compiles
      a == b; // Doesn't compile
    }
    

    使用 C++20,逻辑也方便地扩展到 operator!=。对于 C++20 之前的编译器,您可能还应该为 operator!= 添加相应的重载。

    【讨论】:

    • 嘿,好主意。我现在没有机会尝试,所以我无法测试它,但是:在 C++17 中,&lt;&gt;&lt;=&gt;=- 二进制运算符可以也这样被删除?
    • @JasonC:当然。不过,这不是必需的,因为这些运算符都没有为void*void const* 定义。当然,如果您转换为不同的指针类型,您也可能需要处理它们。 ... 对于 C++20,您可以只使用 operator&lt;[=&gt;,而对于 C++20 之前的版本,您需要处理整套运算符。
    • 谢谢。当我有机会时,我会确保这一切都有效(我相信它确实有效,我只是喜欢亲眼看到它)。也感谢关于void *数学的提醒。当我回到家时,我可能应该将其更改为 char * 或我的示例中的其他内容,以保持事物的代表性。在我的真实代码中,它是一种不同的指针类型。
    【解决方案2】:

    如果您将operator const char * 标记为explicit,那么您的代码将无法编译。

    explicit 需要 C++11,但您已经在使用 nullptr,所以这不是问题。

    【讨论】:

    • 是的;但我想让转换运算符保持隐式。我提到过,但它可能被埋在帖子的长度中。 :)
    • 对不起,我错过了。
    • 在这种情况下,@Dietmar 的回答似乎是正确的方法
    猜你喜欢
    • 1970-01-01
    • 2013-08-25
    • 2017-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-27
    • 2019-01-08
    • 2017-05-28
    相关资源
    最近更新 更多