这是理解转换运算符,模板和模板特化的主要学术练习。以下代码中的转换运算符模板适用于int
,float
和double
,但在与std::string
...一起使用时失败。我已经创建了转换为std::string
的特化,它在初始化std::string s = a;
时使用,但在与强制转换static_cast<std::string>(a)
一起使用时失败。
#include <iostream>
#include <string>
#include <sstream>
class MyClass {
int y;
public:
MyClass(int v) : y(v) {}
template <typename T>
operator T() { return y; };
};
template<>
MyClass::operator std::string() {
std::stringstream ss;
ss << y << " bottles of beer.";
return ss.str();
}
int main () {
MyClass a(99);
int i = a;
float f = a;
double d = a;
std::string s = a;
std::cerr << static_cast<int>(a) << std::endl;
std::cerr << static_cast<float>(a) << std::endl;
std::cerr << static_cast<double>(a) << std::endl;
std::cerr << static_cast<std::string>(a) << std::endl; // Compiler error
}
以上代码在g ++和icc中生成编译器错误,两者都抱怨没有用户定义的转换适合将MyClass
实例转换为std::string
上的static_cast
(C -style演员表现相同)。
如果我用转换运算符的显式非模板版本替换上面的代码,一切都很开心:
class MyClass {
int y;
public:
MyClass(int v) : y(v) {}
operator double() {return y;}
operator float() {return y;}
operator int() {return y;}
operator std::string() {
std::stringstream ss;
ss << y << " bottles of beer.";
return ss.str();
}
};
我的std::string
模板专精有什么问题?为什么它适用于初始化但不能进行转换?
更新
在@ luc-danton的一些模板魔法之后(我以前从未见过的元编程技巧),在启用实验性C ++ 0x扩展后,我在g ++ 4.4.5中使用了以下代码。除了这里所做的恐怖之外,需要实验性的编译器选项才足以让不这样做。无论如何,这对我来说是有教育意义的,因为它对我而言:
class MyClass {
int y;
public:
MyClass(int v) : y(v) {}
operator std::string() { return "nobody"; }
template <
typename T
, typename Decayed = typename std::decay<T>::type
, typename NotUsed = typename std::enable_if<
!std::is_same<const char*, Decayed>::value &&
!std::is_same<std::allocator<char>, Decayed>::value &&
!std::is_same<std::initializer_list<char>, Decayed>::value
>::type
>
operator T() { return y; }
};
这显然迫使编译器为operator std::string()
选择转换std::string
,它会超出编译器遇到的任何歧义。
答案 0 :(得分:9)
您只需使用
即可重现此问题std::string t(a);
结合GCC(error: call of overloaded 'basic_string(MyClass&)' is ambiguous
)的实际错误,我们可以找到有关可能发生的事情的强有力线索:在复制初始化的情况下,有一个首选的转换序列({{ 1}}),并且在直接初始化(std::string s = a;
和std::string t(a);
)的情况下,至少有两个序列,其中一个不能优先于另一个
查看所有带有一个参数的static_cast
显式构造函数(在直接初始化期间只考虑但不复制初始化的那些),我们发现std::basic_string
实际上是唯一的显式构造函数。
不幸的是,除了那个诊断之外我做不了多少:我想不出一个发现是explicit basic_string(const Allocator& a = Allocator());
是否被实例化的技巧(我试过SFINAE和operator std::allocator<char>
,但没有成功),我对函数模板特化,重载决策和库要求知之甚少,以了解GCC的行为是否符合要求。
既然你说这个练习是学术性的,那么我将饶恕你们常用的dia骂,非显性转换操作符不是一个好主意。我认为你的代码是一个很好的例子,无论如何:)
我让SFINAE工作了。如果运算符声明为:
operator std::allocator<char>() = delete;
然后没有歧义,代码将编译,将挑选template <
typename T
, typename Decayed = typename std::decay<T>::type
, typename = typename std::enable_if<
!std::is_same<
const char*
, Decayed
>::value
&& !std::is_same<
std::allocator<char>
, Decayed
>::value
&& !std::is_same<
std::initializer_list<char>
, Decayed
>::value
>::type
>
operator T();
的特化,并且生成的程序将按照需要运行。我仍然没有解释为什么复制初始化很好。
答案 1 :(得分:1)
static_cast
这相当于做std::string(a)
。
请注意std::string s = std::string(a);
也不编译。我的猜测是,构造函数有很多重载,模板版本可以将a
转换为许多合适的类型。
另一方面,使用固定的转换列表,其中只有一个匹配字符串构造函数接受的类型。
要对此进行测试,请将转化添加到const char*
- 非模板化版本应在同一位置开始失败。
(现在的问题是为什么std::string s = a;
有效。这与std::string s = std::string(a);
之间的细微差别只有众神知道。)