【问题标题】:Why I'm not able to prevent the undesirable C-style cast to compile?为什么我不能阻止不受欢迎的 C 风格转换编译?
【发布时间】:2023-03-16 16:42:01
【问题描述】:

有一个不受欢迎的 C 风格转换,我无法阻止它编译。不受欢迎的转换执行从某个类的对象到某个其他类的非常量引用的 C 样式转换。类是不相关的。同时,我喜欢支持从同一类的对象到 const 引用的 C 风格转换。我正在提供一个公共转换运算符来支持理想的演员表。在这种情况下,似乎不可能防止不受欢迎的演员表。
转换为非常量引用失败("Sandbox::B::operator Sandbox::A &()"(在第 30 行声明)不可访问*),不幸的是转换为 const 引用要么失败错误:从“Sandbox::B”到“const Sandbox::A”的转换函数不止一个适用: 函数“沙盒::B::操作员常量沙盒::A &()” 函数“沙盒::B::操作员沙盒::A &()”):

#include <iostream>
#include <string>
#include <cstdlib>

namespace Sandbox {
    class A {
    public:
        A (int i) : _x (i) { }
    private:
        int _x;
    };

    class B {
    public:
        B (const char* m) : _m (m), _a (std::atoi (m)) { }

        /*
         * This one shall be supported.
         */ 
        operator const A& () {
            return _a;
        }
    private:
        /*
         * This one shall be not supported.
         * If this one is disabled both desired and undesired conversions pass the compilation.
         */ 
        operator A& ();

        const std::string _m;
        const A _a;
    };
}

int main () {
    Sandbox::A a (1973);
    Sandbox::B b ("1984");

    /*
     * This is the undesirable cast and it shall fail to compile.
     */
    (Sandbox::A&)b;
    /*
     * This is the desirable cast and it shall pass the compilation.
     */
    (const Sandbox::A&)b;

    return 0;
}

如果我禁用运算符 operator A&amp; () ,则会生成所需和不希望的转换。

我正在使用 gcc、icc 和 MSVC 编译。 我无法控制客户端代码并阻止使用 C 样式转换。

【问题讨论】:

  • 会删除非常量版本并保持 const 版本达到您想要的效果吗?还是会将非常量版本默认为普通的 c 转换?
  • 使用 cast 您可以从任何东西投射到任何东西,对此您无能为力。还是我错了?
  • 为什么要支持 C-style cast?我的意思是,你为什么要编写 C 风格的演员表?当您使用static_cast 并且不定义非常量版本时,一切都按预期进行。
  • 也许我遗漏了一些东西,但我不明白问题的重点。您有无法更改的蹩脚代码,并且您想阻止它编译。然后根本不编译它:)
  • 如果他使用 C 风格的强制转换,你无法避免客户端代码抛弃 const-ness,因为 C 风格的强制转换允许抛弃 const-ness。 Simply don't use them; upgrade your client code to use static_cast.

标签: c++ casting standards conversion-operator


【解决方案1】:

这应该可以解决问题(在 clang3.5 上测试):

#include <iostream>
#include <string>
#include <cstdlib>

namespace Sandbox {
  class A {
  public:
    A (int i) : _x (i) { }

    void        fun()
    {
      std::cout << "action" << std::endl;
    }

  private:
    int _x;
  };

  class B {
  public:
    B (const char* m) : _m (m), _a (std::atoi (m)) { }

    /*
     * This one shall be supported.
     */
    template<typename T, typename Enable = typename std::enable_if<std::is_same<T, A>::value, A>::type>
    operator const T& ()
    {
      return _a;
    }

    /*
     * This one shall be not supported.
     * If this one is disabled both desired and undesired conversions pass the compilation.
     */
  private:
    template<typename T, typename Enable = typename std::enable_if<std::is_same<T, A>::value, A>::type>
    operator T& ();

    const std::string _m;
    const A _a;
  };
}

int main () {
  Sandbox::A a (1973);
  Sandbox::B b ("1984");

  /*
   * This is the undesirable cast and it shall fail to compile.
   */
  (Sandbox::A&)b;

  /*
   * This is the desirable cast and it shall pass the compilation.
   */
  (const Sandbox::A&)b;

  return 0;
}

至于为什么你的版本没有做你想做的,和C-Style cast的规则有关:

当遇到 C 风格的强制转换表达式时,编译器会尝试 以下转换表达式,按此顺序:

a) const_cast(表达式)

b) static_cast(表达式), 带扩展:派生类的指针或引用是 还允许强制转换为指针或引用明确 基类(反之亦然),即使基类不可访问 (也就是说,这个转换忽略了私有继承说明符)。相同的 适用于将指向成员的指针转换为指向成员的指针 明确的非虚基

c) 紧随其后的是 static_cast(带扩展) 通过 const_cast

d) reinterpret_cast(表达式)

e) reinterpret_cast 后跟 const_cast

第一个选择 满足相应演员的要求是 被选中,即使无法编译

免责声明:这个解释主要是基于猜测,有多个步骤和复杂的规则,所以我不确定一切是否真的有效,因为我认为我已经理解了它,但你去吧。 em>

由于您转换为引用,reinterpret_cast 将始终基于其 rules of type aliasing 工作,因此使 C 样式转换失败的唯一方法是在该类型上明确地生成一个 static_cast 产生错误。不幸的是,转换规则似乎并不认为用户定义的转换为const 类型比用户定义的转换为非 cv 限定类型更匹配,即使static_cast 它们都处于同一级别目标类型为 const 合格。而使用模板、SFINAE 和参数推导,以及从山龙中提取的一些魔法编译器粉末,它就可以工作了。 (是的,这一步对我来说也有点神秘)。

【讨论】:

  • 感谢您的精彩和清晰的解释以及您的中继有趣的解决方案。我仍然不明白为什么“转换规则似乎不认为用户定义的转换为 const 类型比用户定义的转换为非 cv 限定类型更匹配”。我正在尝试应用您的解决方案,在我的情况下,我也必须与 boost 相关(因为我使用 C++98),这可能是不可接受的(因为据信广泛使用的接口头文件中的 boost 会增加构建时间)。
  • @Lesh 我猜这是因为使用类型作为其const 版本是一种始终有效的隐式转换,因此当您转换为const T 时,编译器会尝试找到到@987654330 的转换@知道它仍然可以毫无问题地添加const。类似的东西。出于某种原因,添加const 似乎在重载解决方案中算作无成本操作。
  • 我已经检查了您的解决方案。不幸的是,它阻止了以下有效代码的编译:void acceptA (const A&amp; a) { ... } ... Sandbox::B b ("1984"); acceptA(b); //Compilation error at this line 错误:多个重载函数“Sandbox::acceptA”实例与参数列表匹配:函数“Sandbox::acceptA(const Sandbox::A & )" function "Sandbox::acceptA(Sandbox::A &)" 参数类型为:(Sandbox::B)
  • 这个错误可以通过显式转换绕过:`acceptA (static_cast (b));'
  • @Lesh 不应该。您收到的错误消息是说您有 2 个 acceptA 重载,一个采用 const Sandbox::A&amp;,另一个采用“Sandbox::A&”,这是错误的根源,而不是 2 次转换。
猜你喜欢
  • 2011-06-15
  • 2016-01-20
  • 2011-07-05
  • 2021-04-04
  • 1970-01-01
  • 1970-01-01
  • 2010-10-20
  • 2017-11-16
  • 2012-04-08
相关资源
最近更新 更多