代码之家  ›  专栏  ›  技术社区  ›  Nele

C++升级到VS2017:关于不明确调用的错误C2668

  •  1
  • Nele  · 技术社区  · 6 年前

    我正在将一个旧图书馆从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;
    }
    

    我相信我理解这个错误:当我试图从一个变量创建一个日期时,有几种可能的转换,每种转换到不同的日期构造函数,所以调用是不明确的。

    但为什么添加拷贝构造函数可以解决这个问题呢?

    非常感谢您的帮助!

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

    1 回复  |  直到 6 年前
        1
  •  2
  •   gflegar    6 年前

    问题

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

    MSVC编译器碰巧做了一些“神奇的”和非标准的事情来编译它(这是MSVC的一个常见主题)。如果您尝试任何其他主要编译器(gcc、clang、icc),请参阅实时示例 here )他们都没能编译出来。 我不会依赖这种模棱两可的代码,即使它“工作”,因为它可能(也可能会)停止使用另一个编译器版本或不同的编译器。

    这种模糊性来自于C++对潜在隐式转换序列进行排序的方式:它总是尝试进行最小数量的转换,最多是一次用户定义的转换。本标准详细描述了这一过程 [class.conv] .

    就你而言,打电话时 Date t1(cvt); 解决调用有两种方法,每种方法都只需要一个用户定义的转换(而不需要其他转换):

    1. CVariant int ( CVariant::operator int() ),然后打电话给 Date::Date(int) .
    2. 变异的 Date ( CVariant::operator Date() ),然后调用(隐式)复制构造函数 Date::Date(const Date &) .

    解决方案

    有几种方法可以解决此问题:

    1. 添加 explicit 关键字转换为CVariant转换之一,因此它将不再参与隐式转换。
    2. 指定要在调用站点进行的转换(例如。 Date t1(static_cast<Date>(cvt) 使用 CVVariant::运算符日期() ).
    3. 从中添加转换构造函数 变异的 日期 ( Date::Date(const CVariant &) ),这将使此构造函数不需要任何转换,因此编译器将首选此构造函数而不是其他两个。

    如何实施方案3

    请参阅完整示例 here .

    简而言之,您需要执行以下操作:

    • 转发声明 变异的 因此,在中创建转换构造函数时,其名称可用 日期
    • 将构造函数的声明添加到 日期
    • 之后定义构造函数 变异的 已定义,因此您可以使用 Cvariant 日期 在构造函数的实现中

    以下是代码的相关更改:

    class CVariant;
    
    class Date  
    {
    public:
        // [...]
        explicit Date(const CVariant &cvt);
        // [...]
    };
    
    class CVariant
    {
        // [...]
    };
    
    
    Date::Date(const CVariant &cvt) : Date(cvt.operator Date()) {}