转换操作符模板专门化
Conversion operator template specialization
这是一个理解转换操作符、模板和模板专门化的主要学术练习。下面代码中的转换操作符模板适用于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
实例转换为static_cast
上的std::string
(c风格的强制转换行为相同)。
如果我用显式的、非模板版本的转换操作符替换上面的代码,一切都很好:
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; }
};
这显然迫使编译器选择将std::string
转换为operator std::string()
,从而避免了编译器遇到的歧义。
只要使用
就可以重现这个问题std::string t(a);
结合GCC (error: call of overloaded 'basic_string(MyClass&)' is ambiguous
)的实际错误,我们对可能发生的事情有了强有力的线索:在复制初始化 (std::string s = a;
)的情况下有一个首选转换序列,而在直接初始化的情况下(std::string t(a);
和static_cast
)至少有两个序列,其中一个不能优先于另一个。
查看所有接受一个参数的std::basic_string
显式构造函数(在直接初始化而不是复制初始化期间只考虑这些构造函数),我们发现explicit basic_string(const Allocator& a = Allocator());
实际上是唯一的显式构造函数。
不幸的是,除了诊断之外,我不能做太多:我想不出一个技巧来发现operator std::allocator<char>
是否被实例化(我尝试过SFINAE和operator std::allocator<char>() = delete;
,没有成功),我对函数模板专门化,重载解析和库要求了解得太少,无法知道GCC的行为是否符合。
既然你说这个练习是学术性的,我就不像你那样批评非显式转换操作符不是一个好主意了。我认为你的代码是一个足够好的例子,为什么无论如何:)
我让SFINAE工作。如果将操作符声明为:
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();
然后没有歧义,代码将被编译,std::string
的专门化将被选中,结果程序将按照期望的方式运行。我仍然没有解释为什么复制初始化是好的
这里的static_cast
等同于std::string(a)
。
注意std::string s = std::string(a);
也不能编译。我的猜测是,构造函数有很多重载,模板版本可以将a
转换为许多合适的类型。
另一方面,对于固定的转换列表,其中只有一个与字符串的构造函数接受的类型完全匹配。
要测试这一点,添加对const char*
的转换-非模板化版本应该在相同的地方开始失败。
std::string s = a;
工作。这和std::string s = std::string(a);
之间的细微差别只有神知道
- 是否可以对零模板参数进行模板专门化
- 尝试根据类中 typedef 的存在来专门化模板函数
- 如何基于模板化类的基类专门化成员函数
- 如何为指向复杂值的迭代器专门化算法?
- 专门化模板覆盖函数/避免对象切片
- 我能否根据其运算符()的签名专门化可变参数模板参数
- 如何使用模板化类专门化模板化函数?
- 线程 std::调用未知类型,无法专门化函数错误
- 输入两个不专门化大小的矩阵
- 如何在模板类中专门化赋值运算符?
- 如何专门化容器和枚举的模板
- Clang拒绝类模板的嵌套类仅通过专门化定义的代码是正确的吗
- 取消专门化C++模板参数
- SFINAE的变分变量模板专门化
- 类模板专门化的操作符重载
- 转换操作符模板专门化
- 对不同的特性专门化同一个操作符
- 通过继承专门化操作符模板
- 仅为专门化模板类定义转换操作符
- 如何在不重载比较操作符的情况下为std::max专门化自定义类型