2

我有一个类模板CFoo<T>。我想允许隐式转换到 的其他实例CFoo,但仅适用于模板参数是T.

我尝试使用 SFINAE,但我的任何尝试都不适用于我尝试过的任何编译器(VC 2012 或 gcc):

#include <type_traits>

template <class T> class CFoo {
public:
    template <class Q>  operator
     // typename std::enable_if<std::is_base_of<Q, T>::value, CFoo<Q>&>::type   // SHOULD WORK?
     // typename std::enable_if<1, CFoo<Q>&>::type                              // SHOULD WORK?
     CFoo<Q>&  // compiles, but doesn't restrict on Q like I want
     () const {
        return *(CFoo<Q>*)this;
    }
};

class A {};
class B : public A {};

int main(int argc, char* argv[])
{
    CFoo<B> b;
    CFoo<A>& a = b;
    return 0;
}

为什么在 SFINAE 中被注释掉的任何一个尝试都不在这里工作?在这两种情况下,我都会收到一个无效初始化的错误a,就好像我的操作员没有被调用一样。

4

1 回答 1

7

根据[temp.deduct.conv]:

模板参数推导是通过将转换函数模板的返回类型(称为 P)与转换​​结果所需的类型(称为 A;参见 8.5、13.3.1.5 和 13.3.1.6该类型的确定)如 14.8.2.5 中所述。

在简单的情况下:

template <class Q>
operator CFoo<Q>& const;

这很简单,我们尝试推断CFoo<Q>&反对CFoo<A>&. 该部分还有其他规则,但最终该扣除成功Q == A

由于同样的原因,您的其他两次尝试都失败了。我会选择一个更简单的:

template <class Q>
operator typename std::enable_if<1, CFoo<Q>&>::type const;

在这里,我们试图推断typename std::enable_if<1, CFoo<Q>&>::type. 这是一个非推导的上下文(它 是使用限定 ID指定的类型的嵌套名称说明符),因此推导失败。所以这个转换函数不会被考虑,所以赋值失败,因为没有找到转换。

您需要返回类型是推导的上下文,因此 SFINAE 必须到这里:

template <class Q,
          typename = std::enable_if_t<std::is_base_of<Q, T>::value>>
operator CFoo<Q>& const;

这样,我们就可以推断出 ( CFoo<Q>&) - 并且推断可以成功(如果Q是 的基数T):

CFoo<A>& a = b; // OK
CFoo<int>& i = b; // deduction failure on Q, so there's no viable conversion function
                  // so this is an error

也就是说,正如TC指出的那样,虽然我对解决模板谜题很感兴趣,但这确实不是一个好的解决方案,因为:

return *(CFoo<Q>*)this;

只是做了 a reinterpret_cast(和 a const_cast),所以它真的不可能做任何合理的事情,你几乎肯定会(除非CFoo是微不足道的)通过尝试使用错误类型访问其成员来结束未定义的行为。

您可能想要添加转换构造函数而不是转换函数

template <typename Q,
          typename = std::enable_if_t<std::is_base_of<T, Q>::value>>
CFoo(CFoo<Q> const& ) { }

这样,当你这样做时:

CFoo<A> a = b; // a is not a reference anymore

您正在构建一个必然有效的新对象。

于 2015-07-22T17:30:12.607 回答