首页 > 解决方案 > C++升级到VS2017:关于模糊调用的错误C2668

问题描述

我正在将旧库从 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 构造函数,因此调用是模棱两可的。

但是为什么添加一个复制构造函数可以解决这个问题呢?

非常感谢您的帮助!

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

标签: c++visual-studio-2017ambiguous

解决方案


问题

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

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

歧义来自于 C++ 对潜在的隐式转换序列进行排序的方式:它总是尝试做最少的转换序列,最多只做一次用户定义的转换。该标准在[class.conv]中更详细地描述了这个过程。

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

  1. CVariantint( CVariant::operator int()) 的转换,然后调用到Date::Date(int)
  2. CVariantto Date( CVariant::operator Date()) 转换,然后调用(隐式)复制构造函数Date::Date(const Date &)

解决方案

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

  1. explicit关键字添加到 CVariant 转换之一,因此它将不再参与隐式转换。
  2. 指定您希望在呼叫站点进行的转换(例如Date t1(static_cast<Date>(cvt),使用CVariant::operator Date())。
  3. 添加一个 from CVariantto Date( Date::Date(const CVariant &)) 的转换构造函数,这将使这个构造函数不需要转换,因此编译器会更喜欢这个而不是其他两个。

如何实施选项 3

在此处查看完整示例。

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

  • 前向声明CVariant,因此它的名称在创建转换构造函数时可用Date
  • 将构造函数的声明添加到Date
  • 在定义了构造函数之后再定义构造CVariant函数,所以你可以在构造函数的实现中使用从Cvariant到的转换Date

以下是代码的相关更改:

class CVariant;

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

class CVariant
{
    // [...]
};


Date::Date(const CVariant &cvt) : Date(cvt.operator Date()) {}

推荐阅读