我正在将旧库从VS2010升级到VS2017。我遇到了一个可以解决的错误,但是我不明白为什么该修复会起作用。

下面我做了一个小测试,它再现了VS2017中的错误。但是,如果在VS2010中运行它,或者在Date类中取消注释复制构造函数,则它可以正常工作。

我得到的错误:

error.cpp(115): error C2668: 'Date::Date': ambiguous call to overloaded function
error.cpp(22): note: could be 'Date::Date(Date &&)'
error.cpp(22): note: or       'Date::Date(const Date &)'
error.cpp(19): note: or       'Date::Date(std::string)'
error.cpp(18): note: or       'Date::Date(int)'
error.cpp(115): note: while trying to match the argument list '(CVariant)'

编码
#include "stdafx.h"
#include <string>
#include <memory>

class Date
{
public:
    Date() { date = 19000101; }

    // Copy constructor
    // The code will not compile in VS2017 if this constructor is not there,
    // But it compiles fine in VS2010
    /*Date(const Date & dt) {
        date = dt.date;
    }*/
    explicit Date(int yyyymmdd) { date = yyyymmdd; }
    explicit Date(std::string isodate) { date = 19000101; } // Silly  constructor, just for this example
private:
    int date;
};
enum cvtype {
    mInt,
    mDate,
    mNone
};

class CVariant
{
public:
    CVariant() {}

    // Copy constructor
    CVariant(const CVariant& variant) {
        copy_CVariant(variant);
    }

    // Copy assignment
    CVariant& operator=(const CVariant& variant) {
        copy_CVariant(variant);
        return *this;
    }

    void copy_CVariant(const CVariant& variant)
    {
        switch (variant._type)
        {
        case mInt:
            operator=(variant.value._Int);
            break;
        case mDate:
            operator=(*variant.value.pDate);
            break;
        default:
            clear();
            break;
        }
    }

    // Other constructors
    CVariant(const Date& date_value) : _type(mNone) { operator=(date_value);}
    CVariant(int int_value) : _type(mNone) { operator=(int_value); }

    // casting
    operator int() const {
        if (_type == mInt) return value._Int;
        else return 0;
    }
    operator Date() const {
        if (_type == mDate) return *value.pDate;
        return Date();
    }

    // Assignment
    CVariant& operator=(int int_value) {
        clear();
        _type = mInt;
        value._Int = int_value;
        return *this;
    }
    CVariant& operator=(const Date& date_value) {
        clear();
        _type = mDate;
        value.pDate = new Date(date_value);
        return *this;
    }

private:
    void clear()
    {
        if (_type == mDate)
            delete value.pDate;
    }

    union VarValue
    {
        int _Int;
        Date* pDate;
    } value;

    cvtype _type;

};

int main()
{
    Date t(20170516);
    int i(10);
    CVariant cvt(t);
    CVariant cvi(i);
    // The following line only works in VS2017 if
    // you uncomment the copy constructor in the Date class
    // This works fine in VS2010 no matter what
    Date t1(cvt);
    // This works
    Date t2 = cvt;
    Date t3 = cvi;
    int i1 = cvt;
    int i2 = cvi;
    Date t4(cvt.operator Date());
    Date t5 = cvt.operator Date();
    int i3 = cvi;
    return 0;
}

我相信我理解错误:当我尝试从CVariant创建Date时,可能有几种转换,每种转换都可能是不同的Date构造函数,因此调用是模棱两可的。

但是为什么要添加一个拷贝构造函数来解决这个问题呢?

非常感谢您的帮助!

附言我知道使用隐式运算符转换,尤其是对算术类型的隐式运算符转换不是一个好主意,但是我的首要任务是让这个旧库进行编译。

最佳答案

问题

由于调用不明确,带有显式复制构造函数的版本和不带显式复制构造函数的版本都不是有效的C++代码。

碰巧MSVC编译器做了一些“神奇的”和非标准的事情来编译它(MSVC的一个共同主题)。如果尝试使用其他任何主要的编译器(gcc,clang和icc,请参见实时示例here),则它们都将无法编译。
即使它“起作用”,我也不会依赖于这样的模棱两可的代码,因为它可能(并且可能会)停止与其他编译器版本或其他编译器一起工作。

模糊性来自C++对潜在的隐式转换序列进行排序的方式:它总是尝试执行尽可能少的隐式转换序列,并且最多只能执行一次用户定义的转换。该标准在[class.conv]中更详细地描述了此过程。

在您的情况下,调用Date t1(cvt);时,有两种方法可以解决该调用,每种方法仅需要一个用户定义的转换(而无需其他转换):

  • CVariant转换为int(CVariant::operator int()),然后调用Date::Date(int)
  • CVariant转换为Date(CVariant::operator Date()),然后调用(隐式)复制构造函数Date::Date(const Date &)

  • 解决方案

    有几种方法可以解决此问题:
  • explicit 关键字添加到CVariant转换之一,因此它将不再参与隐式转换。
  • 指定您希望在 call 站点进行的转换(例如,使用Date t1(static_cast<Date>(cvt)CVariant::operator Date())。
  • 将转换构造函数从CVariant添加到Date(Date::Date(const CVariant &)),这将使该构造函数不需要任何转换,因此编译器将首选此结构而不是其他两个。

  • 如何实现方案3

    查看完整示例here

    简而言之,您需要执行以下操作:
  • 转发声明CVariant,以便在Date中创建转换构造函数时可以使用其名称
  • 将构造函数的声明添加到Date
  • 在定义了CVariant之后定义了构造函数,因此您可以在构造函数
  • 的实现中使用从CvariantDate的转换

    这是对代码的相关更改:
    class CVariant;
    
    class Date
    {
    public:
        // [...]
        explicit Date(const CVariant &cvt);
        // [...]
    };
    
    class CVariant
    {
        // [...]
    };
    
    
    Date::Date(const CVariant &cvt) : Date(cvt.operator Date()) {}
    

    关于c++ - C++升级到VS2017:关于歧义调用的错误C2668,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/50411219/

    10-11 22:30
    查看更多