Подтвердить что ты не робот

Неявное преобразование типа С++ с шаблоном

У меня есть шаблон class A

template <unsigned int m>
class A
{
public:
    A(int) {}
};

У кого есть конструктор из int. И у меня есть операция:

template<unsigned int m>
A<m> operator+(const A<m>&, const A<m>&)
{
    retrun A<m>(0);
}

Но когда я звоню:

A<3> a(4);
A<3> b = a + 5;
A<3> c = 5 + a;

Я бы хотел, чтобы int был неявно преобразован в A, но компиляторы вызывают ошибку.

Есть ли элегантный способ включить неявное преобразование без использования таких решений, как:

  • a + A<m>(5)
  • operator+<3>(a, 5)
4b9b3361

Ответ 1

Решение уже показано в этом ответе. Теперь, больше о проблеме...

Проблема вашего кода заключается в том, как выполняется разрешение перегрузки. Когда рассматривается функция шаблона для разрешения перегрузки, компилятор будет выполнять вывод типа по аргументам и придумывать подмену типа, которая соответствует вызову, или же он не может применить этот шаблон, удаляет его из набора потенциальных кандидатов и продолжается. Проблема на этом этапе заключается в том, что вывод типа выводит только точные соответствия (с возможной дополнительной константой/неустойчивой квалификацией). Поскольку соответствие является точным, компилятор не будет использовать какое-либо преобразование (опять же, кроме cv).

Самый простой пример этого происходит с функциями std::max и std::min:

unsigned int i = 0;
std::min( i, 10 );    // Error! 

Вывод типа выводит T в template <typename T> min( T const &, T const & ) как unsigned для первого аргумента, но int для второго они отличаются и компилятор отбрасывает эту функцию шаблона.

Решение, предложенное в ответе , использует функцию языка, которая позволяет вам определять функцию друга, не являющуюся членом, внутри определения класса. Преимущество шаблонов заключается в том, что для каждого (различного) создания шаблона компилятор создаст свободную функцию без шаблона на уровне пространства имен, которая имеет подпись, полученную путем подстановки реальных типов экземпляра в объявлении друга:

template <typename T>
class test {
    friend test operator+( test const & lhs, test const & rhs ) {  // [1]
        return test();
    }
}
test<int> t;                                                       // [2]

В приведенном выше примере компилятор позволяет добавить определение функции friend внутри области класса в [1]. Затем, когда вы создаете шаблон в [2], компилятор будет генерировать свободную функцию:

test<int> operator+( test<int> const & lhs, test<int> const & rhs ) { 
   return test<int>();
}

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

Магия здесь имеет несколько сторон. Первая часть состоит в том, что она в целом вы определяете функции без шаблонов для каждого и всех созданных типов, поэтому вы получаете универсальность и в то же время преимущество разрешения перегрузки, способного использовать эту функцию когда аргументы не идеальны.

Поскольку это не шаблонная функция, компилятор может вызывать неявные преобразования для обоих аргументов, и вы получите ожидаемое поведение.

Кроме того, при поиске выполняется другой тип магии, так как определенная функция может быть найдена только в зависимости от зависимостей, зависящих от аргумента, если она также не объявлена ​​на уровне пространства имен, что в нашем случае не может быть сделано общим способом. Импликация этого может быть хорошей или плохой, в зависимости от того, как вы хотите ее рассмотреть...

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

(Подробнее о шаблоне дружбы здесь, но обратите внимание, что в этом конкретном случае все остальные варианты не смогут выполнять неявные преобразования).

Ответ 2

Каждому attemp для предоставления оператора, использующего шаблоны, потребуется как минимум одна секунда перегрузки. Но вы можете избежать этого, указав оператор внутри класса:

template <unsigned int m>
class A
{
public:
  A(int) {}
  inline friend A operator+(const A& a, const A& b) { return A(0); }
};

Работает для обоих, a+5 и 5+a.

Ответ 3

Добавьте этот оператор

template<unsigned int m>
A<m> operator+(const A<m>&, const int&)
{
    return A<m>(0);
}

ИЛИ попробуйте это

template <unsigned int m>
class A
{
friend const A operator+(const A& a, const A& b) { return A(0); }
public:
    A(int) {}
// OR FOR UNARY
    // const A operator+(const A &a) const {return A(0);}
};


int main(){
    A<3> a(4);
    A<3> b = a + 5;
    A<3> c = 5 + a;

}

Ответ 4

Вы можете попробовать добавить к шаблону дополнительный аргумент типа "политика" для вашего класса A, который определит фактический желаемый тип преобразования. Например:

template <unsigned int m, typename ConvVal = int>
class A
{
        public:
                typedef ConvVal conv_val;

                A(ConvVal) {}
};

template<template <unsigned int, class U> class T, unsigned int m, typename U>
T<m, U> operator+(const T<m, U>&, const T<m, U>&)
{
        return T<m, U>(0);
}

template<template <unsigned int, class U> class T, unsigned int m, typename U>
T<m, U> operator+(const T<m, U>&, const typename T<m, U>::conv_val&)
{
        return T<m, U>(0);
}

template<template <unsigned int, class U> class T, unsigned int m, typename U>
T<m, U> operator+(const typename T<m, U>::conv_val&, const T<m, U>&)
{
        return T<m, U>(0);
}

int main()
{
        A<3> a(4);
        A<3> b = a + 5;

        return 0;
}

Теперь ваш класс A примет дополнительный аргумент шаблона, который по умолчанию относится к типу int, и определяет фактический тип, из которого вы разрешите автоматическое преобразование. Вам нужно только перегрузить функцию operator+ три раза, один раз для версии без значения преобразования, которое будет принимать явные классы типа A<m, T>, а другое для двух версий operator+, которые будут использовать типы конверсий. В приведенном выше коде я обобщил это с более универсальными типами, чтобы это можно было сделать практически с любым другим классом, который имеет соответствующую подпись шаблона, и определяет conv_val typedef.