Неявное преобразование типов с шаблоном - PullRequest
37 голосов
/ 20 марта 2012

У меня есть шаблон 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>&)
{
    return 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)

Ответы [ 4 ]

36 голосов
/ 20 марта 2012

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

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

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

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

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

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

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

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

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

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

18 голосов
/ 20 марта 2012

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

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.

1 голос
/ 20 марта 2012

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

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;

}

0 голосов
/ 20 марта 2012

Вы можете попробовать добавить в шаблон дополнительный аргумент типа "policy" для вашего класса 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.

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