隐式转换运算符隐藏规则

时间:2011-07-04 23:38:08

标签: templates gcc c++11

我能看到介绍我的问题的唯一方法是首先提供一个例子:

template<typename T>
class foo
{
    public:
        foo()
        {}

        foo(const foo&)
        {}
};

template<template<typename> class C>
class convertible_to
{
    public:
        template<typename T>
        operator C<T> ();
};

class convertible_to_foo : public convertible_to<foo>
{
        public:
        template<typename T>
        operator foo<T>()
        {
            return foo<T>();
        }
};

我希望foo中声明的隐式转换运算符convertible_to_foo会隐藏(即重载)隐式转换运算符foo中声明的convertible_to,但是GCC 4.6未能接受以下行

convertible_to_foo f;
foo<int> ff(f);

并抱怨从convertible_to_foofoo<int>的转换含糊不清。 这是预期的行为还是GCC在这里可能出错?

感谢您阅读此问题。

修改

要理解为什么我想使用这种看似奇怪的技巧,请参考下面针对karrek的评论并查看下面的用例:

考虑以下课程:

template<typename TYPE>
class real;

template<typename TYPE>
class complex;

我正在设计以最小化由于值调节引起的异常,即域错误。例如,不是允许发生错误,而是将函数sqrt应用于类real的对象将始终返回complex类型的对象。

到目前为止一直很好,但现在有一些预定义的常量,比如pi或复杂的i,这是个好主意。最简单的方法显然是将它们声明如下:

real<double> pi = 3.14...;

但是,作为一个(也许是)完美主义程序员,我意识到这种方法存在一些问题:

1 - 例如,需要高精度pi的应用程序不会受益于使用真正的

2 - 关注内存使用的应用程序将无法使用此pi,因为使用real类型的对象操作它将产生类型为real的对象。 (好的,每次操作发生时明确强制转换为真实是我想避免的丑陋)

我看到解决这个问题的最明智的方法是设计通过隐式转换运算符懒得正确评估自己的常量:

template<template<typename> class C>
class scalar_constant
{
    public:
        scalar_constant& operator = (const scalar_constant&) = delete;

        template<typename T>
        operator C<T> () const;
};

class pi_t : public scalar_constant<real>, public scalar_constant<complex>
{
    public:
        template<typename T>
        operator real<T> () const
        {
            return {std::acos(static_cast<T>(-1))};
        }

        template<typename T>
        operator complex<T> () const
        {
            return {std::acos(static_cast<T>(-1))};
        }
};

const pi_t pi = pi_t();

在这里,这个“概念检查”绝对重要,因为我不会为我决定提供的每个常数重载每个运算符。这样,我可以简单地为运算符提供SFINAE启用的重载,这只是继承“概念”以提供新常量的问题。

我显然可以删除基类上的未定义转换运算符,问题就可以解决了,但是它会失去整个概念的想法,这是为了强制执行概念检查,使程序可能无法链接未定义的继承函数,以及让程序员(懒惰的我)从现在起几年后回到这个代码更容易,并且能够通过查看概念本身并实现来添加另一个概念兼容类应该提供什么。

2 个答案:

答案 0 :(得分:2)

  

我希望在convertible_to_foo中声明的隐式转换运算符foo会隐藏,即重载,

这混淆了条款。 隐藏意味着当您查看派生类时,您只能看到派生的转换函数而不是基础。要重载意味着你会看到两个函数。如果派生函数与基函数具有相同的名称,则派生函数将隐藏函数中的函数。

转换函数名称的规则是:如果它们转换为相同类型,则其中两个相同。因此,如果在基数中你有operator int,并且在派生类中,那么派生的函数将隐藏基数的函数。如果派生的那个是operator float,那么你就没有隐藏。您也没有重载,但是如果您在派生类中查找转换函数operator int,那么您会找到基类的一个,因为它没有被具有相同名称的派生函数隐藏。

对于模板,它的工作方式相同

struct A {
  template<typename T, typename U = int>
  operator T();
};

struct B : A {
  template<typename T = int, typename U>
  operator U();
};

在这种情况下,如果您尝试将B转换为int,则会产生歧义,因为这两个函数都会被采用,并且这两个函数最终都会转换为int模板参数推断完成后(TU的类型不同 - 第一个是第一个位置的模板参数,第二个是第二个位置的模板参数。所以不会隐藏发生)。

在你的情况下,它是相似的,但我不同意GCC的行为。实例化convertible_to<foo>将实例化该模板的成员声明,​​并且一个成员将恰好是

template<typename T>
operator foo<T> (); // after substituting "foo" for "C"

派生类中的转换函数转换为完全相同的类型,因此我不认为GCC应该在这里产生歧义。

答案 1 :(得分:0)

回答问题的第一部分。在此代码中

template<template<typename> class C>
class convertible_to
{
    public:
        template<typename T>
        operator C<T> ();
};

class convertible_to_foo : public convertible_to<foo>
{
        public:
        template<typename T>
        operator foo<T>()
        {
            return foo<T>();
        }
};

如果您从operator C<T>继承,我们真的不能期望隐藏convertible_to<foo>,但如果您继承自convertible_to<bar>,则可见。

这将使C ++和模板比现在更加复杂。所以没有这样的规则。