Operador de classe modelada + tipo de retorno de sobrecarga

8

Estou tentando criar uma classe num modelada. Essa classe precisa ter um atributo público val, com o tipo T, que é o único parâmetro modelado. Além disso, se alguém fornecer um valor, o atributo ( val) deverá ser inicializado com esse valor. Para fazer isso, criei o seguinte código:

#include <iostream>

template<class T>
class Num {
public: 
    T val;

    Num():val(0) { std::cout<<"default constr used"<<std::endl; }
    Num(T value):val(value) {std::cout<<"constr (T value) used"<<std::endl; }
    ~Num() { std::cout<<"destructor used"<<std::endl; }

    template<typename U>
    Num operator+(const Num<U>& other) {
        return val+other.value;
    }
};

Além disso, criei a main()função para testar o programa, que se parece com isso:

int main() {
    std::cout << Num<int>(1) + Num<double>(2.0);
    return 0;
}

No entanto, o resultado do programa é agora 3. Considerando que eu esperava que fosse 3.0(do tipo double).

Guillaume Racicot
fonte
Se você deseja duplicar, já que o código foi escrito agora, será necessário inverter esses operandos. Num<int>(1) + Num<double>(2.0);é o mesmo que Num<int>(1).operator+(Num<double>(2.0))o que você já declaradas como retornando um valor do tipo Num<int>.
precisa
1
Isso nem será compilado como é.
Ted Lyngmo
Por que isso daria um erro de compilação?
Juan Carlos Ramirez
3
@JuanCarlosRamirez Porque a classe não possui uma variável de membro chamada value.
Ted Lyngmo
Você está certo, não viu isso.
Juan Carlos Ramirez

Respostas:

10

Para isso, você precisará alterar o tipo de retorno.

No seu código:

// vvv---- Means Num<T>
   Num operator+(const Num<U>& other) {
       return val + other.val;
   }

De fato, dentro de um modelo de classe, você pode digitar o nome da classe sem argumentos de modelo e será algo equivalente a escrever Num<T> .

Sua função está sempre retornando o tipo do primeiro operante, independentemente do tipo da adição em si.

O que você deseja é deduzir esse tipo proveniente da adição:

auto operator+(const Num<U>& other) -> Num<decltype(val + other.val)> {
    return val + other.val;
}

Dessa forma, é sempre o tipo de retorno correto de acordo com as regras do operador C ++.

Guillaume Racicot
fonte
2
no c ++ 14 e posterior, você não precisa #-> Num<decltype(val + other.val)>
10/1019
@ BЈовић, ele precisa disso. Caso contrário, o tipo de retorno seria decltype(val + other.val)e não Num<decltype(val + other.val)>.
Evg
@ BЈовић Em C ++ 17 eu usaria retorno tipo dedução e modelo de classe argumento dedução para voltarNum{val + other.val}
Guillaume Racicot
9

operator+deve ser simétrico em relação a seus argumentos. É melhor ser implementado como uma função livre, em vez de uma função membro, para tornar essa simetria explícita.

Por exemplo (usando a dedução de tipo de retorno C ++ 14):

template<class T, class U>
auto operator+(const Num<T>& x, const Num<U>& y) {
    using R = decltype(std::declval<T>() + std::declval<U>());
    return Num<R>{x.val + y.val};
}

std::declval<T>()existe para genéricos , se Te / ou Unão são construtíveis por padrão. Se os tipos forem limitados aos incorporados, como inte double, ele poderá ser substituído por T{}ou T():

using R = decltype(T{} + U{});

Com a dedução de argumento do modelo de classe no C ++ 17, pode ser simplificado ainda mais:

template<class T, class U>
auto operator+(const Num<T>& x, const Num<U>& y) {
    return Num{x.val + y.val};
}
Evg
fonte
Uau, eu não tinha ideia de que você poderia autoafastar um tipo de retorno. Isso é bem legal.
precisa
@ scohe001, é o recurso C ++ 14.
Evg