C ++升级到VS2017:关于模糊调用的错误C2668

时间:2018-05-18 12:19:49

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

我正在将一个旧库从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构造函数,因此调用是不明确的。

但为什么添加复制构造函数会解决这个问题?

非常感谢您的帮助!

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

1 个答案:

答案 0 :(得分:2)

问题

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

恰好MSVC编译器做了一些“神奇”和非标准的编译(与MSVC共同的主题)。如果您尝试任何其他主要编译器(gcc,clang,icc,请参阅实例here),它们都无法编译它。 即使它“有效”,我也不会依赖这种模糊的代码,因为它可能(并且可能会)停止使用其他编译器版本或不同的编译器。

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

在您的情况下,在呼叫Date t1(cvt);时,有两种方法可以解决呼叫,每种方法只需要一次用户定义的转换(而不需要其他转换):

  1. CVariant转换为intCVariant::operator int()),然后致电Date::Date(int)
  2. CVariant转换为DateCVariant::operator Date()),然后调用(隐式)复制构造函数Date::Date(const Date &)
  3. 解决方案

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

    1. explicit关键字添加到其中一项CVariant转化中,以便它不再参与隐式转化。
    2. 指定您在呼叫网站上需要的转化(例如Date t1(static_cast<Date>(cvt)使用CVariant::operator Date())。
    3. CVariant添加转换构造函数到DateDate::Date(const CVariant &)),这将使此构造函数不需要转换,因此编译器会更喜欢这个而不是其他两个。< / LI>

      如何实施选项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()) {}