правила скрытого оператора преобразования - PullRequest
2 голосов
/ 05 июля 2011

Единственный способ, с помощью которого я могу представить свой вопрос, это сначала привести пример:

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_foo в foo<int> неоднозначно. Это ожидаемое поведение, или GCC здесь может быть неправильным?

Спасибо, что прочитали этот вопрос.

EDIT

Чтобы понять, почему я хотел бы использовать такую, по-видимому, странную технику, пожалуйста, обратитесь к моему комментарию ниже, направленному на karrek, и посмотрите на пример использования ниже:

Рассмотрим следующие классы:

template<typename TYPE>
class real;

template<typename TYPE>
class complex;

, который я проектирую таким образом, чтобы минимизировать исключения из-за обусловленности значения, то есть ошибок домена. Например, вместо того, чтобы допустить ошибку, применение функции sqrt к объекту класса real всегда будет возвращать объект типа complex.

Пока все хорошо, но сейчас было бы неплохо иметь некоторые предопределенные константы, такие как pi или комплекс i. Очевидно, что самым простым способом было бы объявить их следующим образом:

real<double> pi = 3.14...;

Но затем, как (возможно, тоже) программист-перфекционист, я понимаю, что у этого подхода есть пара проблем:

1 - Приложение, для которого требуется, например, высокая точность числа пи, не получит преимущества от использования реального

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 ]

2 голосов
/ 05 июля 2011

Я ожидаю, что оператор неявного преобразования в foo, объявленный в convertible_to_foo, будет скрывать, то есть перегрузить,

Это смешивает условия. hide означает, что когда вы смотрите в производный класс, вы видите только функцию преобразования производного, а не основание. перегрузка означает, что вы увидите обе функции. Функция в производном будет скрывать функцию в базе, если производная функция имеет то же имя, что и базовая функция.

Правило для имен функций преобразования таково: два из них являются одинаковыми, если они преобразуются в один и тот же тип. Таким образом, если в базе у вас есть 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 после того, как будет сделан вывод аргумента шаблона. (T - это тип, отличный от U - первый - это параметр шаблона в первой позиции, а второй - это параметр шаблона во второй позиции. Поэтому скрытие не будет).

В вашем случае это похоже, но я не согласен с поведением GCC. Создание экземпляра convertible_to<foo> приведет к созданию объявлений членов этого шаблона, и один из членов окажется

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

Функция преобразования в производном классе конвертирует в точно такой же тип, поэтому я не думаю, что GCC должен привести к неоднозначности здесь.

0 голосов
/ 05 июля 2011

Чтобы ответить на первую часть вашего вопроса. В этом коде

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 ++ и шаблоны еще более сложными, чем сейчас. Так что таких правил нет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...