C++ 运算符重载 - 从类进行强制转换

发布于 2024-07-30 17:14:51 字数 1248 浏览 5 评论 0原文

在将 Windows 代码移植到 Linux 时,我在 GCC 4.2.3 中遇到了以下错误消息。 (是的,我知道它是一个稍微旧的版本,但我无法轻松升级。)

main.cpp:16: error: call of overloaded ‘list(MyClass&)’ is ambiguous
/usr/include/c++/4.2/bits/stl_list.h:495: note: candidates are: std::list<_Tp, _Alloc>::list(const std::list<_Tp, _Alloc>&) [with _Tp = unsigned char, _Alloc = std::allocator<unsigned char>]
/usr/include/c++/4.2/bits/stl_list.h:484: note:                 std::list<_Tp, _Alloc>::list(size_t, const _Tp&, const _Alloc&) [with _Tp = unsigned char, _Alloc = std::allocator<unsigned char>]

我使用以下代码来生成此错误。

#include <list>
class MyClass
    {
    public:
        MyClass(){}

        operator std::list<unsigned char>() const { std::list<unsigned char> a; return a; }
        operator unsigned char() const { unsigned char a; return a; }

    };

    int main()
    {
        MyClass a;
        std::list<unsigned char> b = (std::list<unsigned char>)a;

        return 0;
    }

有人经历过这个错误吗? 更重要的是,如何绕过它? (当然,通过使用 GetChar()GetList() 等函数可以完全避免重载,但我想避免这种情况。)

(顺便说一下,删除“operator unsigned char()”可以消除错误。)

While porting Windows code to Linux, I encountered the following error message with GCC 4.2.3. (Yes, I'm aware that it's a slight old version, but I can't easily upgrade.)

main.cpp:16: error: call of overloaded ‘list(MyClass&)’ is ambiguous
/usr/include/c++/4.2/bits/stl_list.h:495: note: candidates are: std::list<_Tp, _Alloc>::list(const std::list<_Tp, _Alloc>&) [with _Tp = unsigned char, _Alloc = std::allocator<unsigned char>]
/usr/include/c++/4.2/bits/stl_list.h:484: note:                 std::list<_Tp, _Alloc>::list(size_t, const _Tp&, const _Alloc&) [with _Tp = unsigned char, _Alloc = std::allocator<unsigned char>]

I'm using the following code to generate this error.

#include <list>
class MyClass
    {
    public:
        MyClass(){}

        operator std::list<unsigned char>() const { std::list<unsigned char> a; return a; }
        operator unsigned char() const { unsigned char a; return a; }

    };

    int main()
    {
        MyClass a;
        std::list<unsigned char> b = (std::list<unsigned char>)a;

        return 0;
    }

Has anyone experienced this error? More importantly, how to get around it? (It's possible to completely avoid the overload, sure, by using functions such as GetChar(), GetList() etc, but I'd like to avoid that.)

(By the way, removing "operator unsigned char()" removes the error.)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

凉城已无爱 2024-08-06 17:14:51

歧义来自于cast-expression的解释。

选择转换时,编译器首先考虑 static_cast 样式转换,并考虑如何解析如下所示的初始化:

std::list<unsigned_char> tmp( a );

这种构造是不明确的,因为 a 有一个用户定义的到 std::listunsigned char 的转换和 std::list 都有一个构造函数它采用 const std::list& 和采用 size_t 的构造函数(可以将 unsigned char 提升为该构造函数) )。

当转换为 const std::list& 时,会考虑此初始化:

const std::list<unsigned_char>& tmp( a );

在这种情况下,当用户定义转换为 std::list 时选择 code> 后,新引用可以直接绑定到转换结果。 如果用户定义的到 unsigned char 的转换,其中选择了 std::list 类型的临时对象,则必须创建此选项,这会使此选项变得更糟转换顺序比前一个选项要好。

The ambiguity comes from the interpretation of the cast-expression.

When choosing the conversion, the compiler first considers a static_cast style cast and considers how to resolve an initialization which looks like this:

std::list<unsigned_char> tmp( a );

This construction is ambiguous as a has a user-defined conversion to a std::list<unsigned char> and to an unsigned char and std::list<unsigned char> has both a constructor which takes a const std::list<unsigned char>& and a constructor which takes size_t (to which an unsigned char can be promoted).

When casting to a const std::list<unsigned_char>&, this initialization is considered:

const std::list<unsigned_char>& tmp( a );

In this case, when the user-defined conversion to std::list<unsigned_char> is chosen, the new reference can bind directly to the result of the conversion. If the user-defined conversion to unsigned char where chosen a temporary object of type std::list<unsigned char> would have to be created and this makes this option a worse conversion sequence than the former option.

滿滿的愛 2024-08-06 17:14:51

我将您的示例简化为以下内容:

typedef unsigned int size_t;

template <typename T>
class List
{
public:
  typedef size_t  size_type;
  List (List const &);
  List (size_type i, T const & = T());
};

typedef List<unsigned char> UCList;

class MyClass
{
public:
  operator UCList const () const;
  operator unsigned char () const;
};

void foo ()
{
  MyClass mc;
  (UCList)mc;
}

第一点是标准定义 C 样式转换应使用更合适的 C++ 样式转换,在本例中为 static_cast。 所以上面的强制转换相当于:

static_cast<UCList> (mc);

static_cast 的定义说:

可以使用以下形式的 static_cast 将表达式 e 显式转换为类型 T
static_cast(e) 如果声明 "T t(e);" 格式正确,对于某些发明的临时变量
t(8.5)

因此,强制转换的语义与以下内容相同:

UCList tmp (mc);

从 13.3.1.3 开始,我们得到了可以在 UCList 中使用的候选构造函数集:

UCList (UCList const &)              #1
UCList (size_type, T const & = T()); #2

接下来发生的是两个单独的重载解析步骤,每个转换运算符一个。

转换为#1:如果目标类型为UCList const &,重载决策会在以下转换运算符之间进行选择。:“operator UCList const () code>”和“operator unsigned char ()”。 使用 unsigned char 需要额外的用户转换,因此对于此重载步骤来说不是一个可行的函数。 因此重载决策成功,并将使用operator UCList const ()

转换为#2:目标类型为size_t。 默认参数不参与重载决策。 重载解析再次在转换运算符之间进行选择:“operator UCList const ()”和“operator unsigned char ()”。 这次没有从 UCList 到 unsigned int 的转换,因此这不是一个可行的函数。 unsigned char 可以提升为 size_t,因此这次重载决策成功,并将使用“operator UCList const ()”。

但是,现在回到顶层,有两个单独且独立的重载解析步骤已成功从 mc 转换为 UCList。 因此,结果是不明确的。

为了解释最后一点,此示例与正常的重载解析情况不同。 通常,实参和形参类型之间存在 1:n 关系:

void foo (char);
void foo (short);
void foo (int);

void bar() {
  int i;
  foo (i);
}

这里有 i=>chari=>shorti=>3。 int。 通过重载决策对它们进行比较,并选择 int 重载。

在上面的例子中,我们有一个 m:n 关系。 该标准概述了为每个单独的参数和所有“n”参数选择的规则,但这就是它的结束,它没有指定我们应该如何决定使用不同的“m”参数。

希望这有一定道理!

更新:

这里的两种初始化语法是:

UCList t1 (mc);
UCList t2 = mc;

“t1”是直接初始化(13.3.1.3),并且所有构造函数都包含在重载集中。 这几乎就像有多个用户定义的转换一样。 有一组构造函数和一组转换运算符。 (即 m:n)。

在“t2”的情况下,语法使用复制初始化(13.3.1.4)并且规则不同:

在 8.5 中指定的条件下,作为类类型对象的复制初始化的一部分,用户定义
可以调用 conversion 将初始化表达式转换为正在初始化的对象的类型。
重载解析用于选择要调用的用户定义转换

在这种情况下,只需键入一个,UCList,因此只需考虑一组转换运算符重载,即。 我们不考虑 UCList 的其他构造函数。

I've simplified your example to the following:

typedef unsigned int size_t;

template <typename T>
class List
{
public:
  typedef size_t  size_type;
  List (List const &);
  List (size_type i, T const & = T());
};

typedef List<unsigned char> UCList;

class MyClass
{
public:
  operator UCList const () const;
  operator unsigned char () const;
};

void foo ()
{
  MyClass mc;
  (UCList)mc;
}

The first point, is that the standard defines that the C-style cast should use the more appropriate C++ style cast, and in this case that's static_cast. So the above cast is equivalent to:

static_cast<UCList> (mc);

The definition of static_cast says:

An expression e can be explicitly converted to a type T using a static_cast of the form
static_cast<T>(e) if the declaration "T t(e);" is well-formed, for some invented temporary variable
t (8.5)

So the semantics for the cast are the same as for:

UCList tmp (mc);

From 13.3.1.3 we get the set of candidate constructors that we can use in UCList:

UCList (UCList const &)              #1
UCList (size_type, T const & = T()); #2

What happens next is two separate overload resolution steps, one for each conversion operator.

Converting to #1: With a target type of UCList const &, overload resolution selects between the following conversion operators.: "operator UCList const ()" and "operator unsigned char ()". Using unsigned char would require an additional user conversion and so is not a viable function for this overload step. Therefore overload resolution succeeds and will use operator UCList const ().

Converting to #2: With a target type of size_t. The default argument does not take part in overload resolution. Overload resolution again selects between the conversion operators: "operator UCList const ()" and "operator unsigned char ()". This time there is no conversion from UCList to unsigned int and so that is not a viable function. An unsigned char can be promoted to size_t and so this time overload resolution succeeds and will use "operator UCList const ()".

But, now back at the top level there are two separate and independent overload resolution steps that have successfully converted from mc to UCList. The result is therefore ambiguous.

To explain that last point, this example is different to the normal overload resolution case. Normally there is a 1:n relationship between argument and parameter types:

void foo (char);
void foo (short);
void foo (int);

void bar() {
  int i;
  foo (i);
}

Here there is i=>char, i=>short and i=>int. These are compared by overload resolution and the int overload would be selected.

In the above case we have an m:n relationship. The standard outlines the rules to select for each individual argument and all of the 'n' parameters, but that's where it ends, it does not specify how we should decide between using the different 'm' arguments.

Hope this makes some sense!

UPDATE:

The two kinds of initialization syntax here are:

UCList t1 (mc);
UCList t2 = mc;

't1' is a direct initialiation (13.3.1.3) and all constructors are included in the overload set. This is almost like having more than one user defined conversion. There are the set of constructors and the set of conversion operators. (ie. m:n).

In the case of 't2' the syntax uses copy-initialization (13.3.1.4) and the rules different:

Under the conditions specified in 8.5, as part of a copy-initialization of an object of class type, a userdefined
conversion can be invoked to convert an initializer expression to the type of the object being initialized.
Overload resolution is used to select the user-defined conversion to be invoked

In this case there is just one to type, UCList, and so there is only the set of conversion operator overloads to consider, ie. we do not consider the other constructors of UCList.

榆西 2024-08-06 17:14:51

如果删除强制转换,它可以正确编译,并且我已经检查了运算符 std::list 是否正在执行。

int main()
{
    MyClass a;
    std::list<unsigned char> b = a;

    return 0;
}

或者如果你将它转换为 const 引用。

    int main()
    {
        MyClass a;
        std::list<unsigned char> b = (const std::list<unsigned char>&)a;

        return 0;
     }

It compiles properly if you remove the cast, and I've checked that the operator std::list is being executed.

int main()
{
    MyClass a;
    std::list<unsigned char> b = a;

    return 0;
}

Or if you cast it to a const reference.

    int main()
    {
        MyClass a;
        std::list<unsigned char> b = (const std::list<unsigned char>&)a;

        return 0;
     }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文