转换运算符模板专业化

时间:2011-10-12 14:18:46

标签: c++ templates metaprogramming template-specialization

这是理解转换运算符,模板和模板特化的主要学术练习。以下代码中的转换运算符模板适用于intfloatdouble,但在与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,它会超出编译器遇到的任何歧义。

2 个答案:

答案 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);之间的细微差别只有众神知道。)