std::to_string - 有多个重载函数与参数列表匹配

54

counter 是一个 int

void SentryManager::add(std::string name,std::shared_ptr<Sentry>){
    name = name + std::to_string(counter);
}

什么是停止这个错误的最佳方式?当我变懒时,我只是将整数long long(或其他东西)强制转换,但我相信有更好的解决方法。

错误信息:

sentrymanager.cpp(8): error C2668: 'std::to_string' : ambiguous call to overloaded function

我正在使用Visual C++ 2010 Express。


能否提供实际的错误信息和你使用的编译器和版本(在GCC 4.5中无法复制)。 - Mat
2个回答

88
在VC++ 2010中,有三个重载的std::to_string函数,分别接受long longunsigned long longlong double类型的参数——显然int不属于这些类型,也没有一种转换比另一种更好(demo),因此不能隐式/明确地进行转换。
就C++11的实际支持而言,这是VC++ 2010标准库实现的失败——C++11标准本身实际上要求有九个std::to_string的重载([string.conversions]/7)。
string to_string(int val);
string to_string(unsigned val);
string to_string(long val);
string to_string(unsigned long val);
string to_string(long long val);
string to_string(unsigned long long val);
string to_string(float val);
string to_string(double val);
string to_string(long double val);
如果所有这些重载都存在,显然您就不会有这个问题。然而,VC++ 2010并非基于实际的C++11标准(因为在发布时它还不存在),而是基于2009年的N3000,该标准不要求这些额外的重载。因此,在这里过分责备VC++是不公平的......
无论如何,对于只有少数调用,使用强制转换来解决歧义是没有问题的:
void SentryManager::add(std::string& name, std::shared_ptr<Sentry>) {
    name += std::to_string(static_cast<long long>(counter));
}

或者,如果你的代码库中有大量使用 std::to_string,可以编写一些包装器并使用它们替代 - 这样,就不需要在调用站点进行强制类型转换:

#include <type_traits>
#include <string>

template<typename T>
inline
typename std::enable_if<std::is_integral<T>::value && std::is_signed<T>::value, std::string>::type
to_string(T const val) {
    return std::to_string(static_cast<long long>(val));
}

template<typename T>
inline
typename std::enable_if<std::is_integral<T>::value && std::is_unsigned<T>::value, std::string>::type
to_string(T const val) {
    return std::to_string(static_cast<unsigned long long>(val));
}

template<typename T>
inline typename std::enable_if<std::is_floating_point<T>::value, std::string>::type
to_string(T const val) {
    return std::to_string(static_cast<long double>(val));
}

// ...

void SentryManager::add(std::string& name, std::shared_ptr<Sentry>) {
    name += to_string(counter);
}

我无法检查VC++ 2010是否成功使用上述SFINAE;如果它失败了,以下使用标签分派而不是SFINAE的方法应该是可以编译的(如果可能会更不清晰):

#include <type_traits>
#include <string>

namespace detail {
    template<typename T>                   // is_float         is_unsigned
    inline std::string to_string(T const val, std::false_type, std::false_type) {
        return std::to_string(static_cast<long long>(val));
    }

    template<typename T>                   // is_float         is_unsigned
    inline std::string to_string(T const val, std::false_type, std::true_type) {
        return std::to_string(static_cast<unsigned long long>(val));
    }

    template<typename T, typename _>       // is_float
    inline std::string to_string(T const val, std::true_type, _) {
        return std::to_string(static_cast<long double>(val));
    }
}

template<typename T>
inline std::string to_string(T const val) {
    return detail::to_string(val, std::is_floating_point<T>(), std::is_unsigned<T>());
}

3
所有的重载函数在 Visual C++ 11 Beta 中都已提供。这并不是标准库实现的缺陷:其中一些重载函数是在 Visual C++ 2010 发布之后添加到 C++11 中的。 - James McNellis
1
缺陷报告1261发生在2009年;我认为怪微软慢动作还是有点道理的,至少可以这么说。 - zwol
2
选择实现标准的草案版本,并在此之后不关注错误修复,这并不是我所谓的最佳实践。IE团队也会这样做,这让人感到非常愤怒。 - zwol
1
@Zack:嗯,没有人指责微软遵循最佳实践... ;-] - ildjarn
1
顺便提一下,在CentOS-6上,g++版本4.4.7也存在同样的问题(并且与上述提到的MSVC编译器来自类似的时间框架)。 - Trevor Boyd Smith
显示剩余2条评论

11
您被绊倒了 C++ DR 1261,其中一部分内容如下:

代码 "int i; to_string(i);" 无法编译,因为 'int' 可以是 'long long' 和 'long long unsigned' 中的任意一个。期望用户将数字强制转换为更大的类型后再使用 to_string 是不合理的。

建议解决此问题的方法是添加更多重载版本。GCC已经实现了这个功能,但我猜 MSVC 还没有。

4
这个解决方案已纳入最终的C++11标准。Visual C++ 11 Beta包含所有这些重载函数。 - James McNellis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接