Executar função dentro do modelo de função apenas para os tipos que têm a função definida

13

Eu tenho um modelo de função que leva muitos tipos diferentes como entrada. Fora desses tipos, apenas um deles tem uma getInt()função. Portanto, eu quero que o código execute a função apenas para esse tipo. Por favor, sugira uma solução. obrigado

#include <type_traits>
#include <typeinfo>

class X {
    public:
    int getInt(){
        return 9;
    }
};

class Y{

};

template<typename T>
void f(T& v){
    // error: 'class Y' has no member named 'getInt'
    // also tried std::is_same<T, X>::value 
    if(typeid(T).name() == typeid(X).name()){
        int i = v.getInt();// I want this to be called for X only
    }
}

int main(){
    Y y;
    f(y);
}
Sumit
fonte
Não relacionado ao seu problema, mas a type_infoestrutura possui um operador de comparação de igualdade , portanto, typeid(T) == typeid(X)deve funcionar também.
Algum programador
5
Uso: if constexprcom condição is_same_v<T,X>.
rafix07
A solução para isso oficialmente ficará mais elegante ainda este ano com a Concepts. Não é super útil agora, eu sei.
sweenish
Existem muitas maneiras de resolver seu problema. Um casal mencionado acima. Você também pode usar traços de variantes diferentes para ver se um tipo tem um getIntmembro que pode ser chamado . Deve haver algumas perguntas aqui apenas no stackoverflow.com sobre como ver se uma estrutura ou classe tem uma função de membro específica, se você apenas pesquisar um pouco.
Algum programador
11
relacionada stackoverflow.com/questions/257288/...
idclev 463035818

Respostas:

10

Se você deseja chamar uma função fpara todos os tipos que possuem membros da função getInt, não apenas X, você pode declarar 2 sobrecargas para a função f:

  1. para tipos que possuem getIntfunção de membro, incluindo classeX

  2. para todos os outros tipos, incluindo classe Y.

Solução C ++ 11 / C ++ 17

Tendo isso em mente, você pode fazer algo assim:

#include <iostream>
#include <type_traits>

template <typename, typename = void>
struct has_getInt : std::false_type {};

template <typename T>
struct has_getInt<T, std::void_t<decltype(((T*)nullptr)->getInt())>> : std::is_convertible<decltype(((T*)nullptr)->getInt()), int>
{};

class X {
public:
    int getInt(){
        return 9;
    }
};

class Y {};

template <typename T,
          typename std::enable_if<!has_getInt<T>::value, T>::type* = nullptr>
void f(T& v) {
    // only for Y
    std::cout << "Y" << std::endl;
}

template <typename T,
          typename std::enable_if<has_getInt<T>::value, T>::type* = nullptr>
void f(T& v){
    // only for X
    int i = v.getInt();
    std::cout << "X" << std::endl;
}

int main() {
    X x;
    f(x);

    Y y;
    f(y);
}

Confira ao vivo .

Observe que std::void_té introduzido no C ++ 17, mas se você estiver limitado ao C ++ 11, é realmente fácil de implementar void_tpor conta própria:

template <typename...>
using void_t = void;

E aqui está a versão C ++ 11 ao vivo .

O que temos em C ++ 20?

O C ++ 20 traz muitas coisas boas e uma delas é o conceito . Acima do que é válido para C ++ 11 / C ++ 14 / C ++ 17 pode ser reduzido significativamente em C ++ 20:

#include <iostream>
#include <concepts>

template<typename T>
concept HasGetInt = requires (T& v) { { v.getInt() } -> std::convertible_to<int>; };

class X {
public:
    int getInt(){
        return 9;
    }
};

class Y {};

template <typename T>
void f(T& v) {
    // only for Y
    std::cout << "Y" << std::endl;
}

template <HasGetInt T>
void f(T& v){
    // only for X
    int i = v.getInt();
    std::cout << "X" << std::endl;
}

int main() {
    X x;
    f(x);

    Y y;
    f(y);
}

Confira ao vivo .

NutCracker
fonte
Antes do C ++ 17, essa implementação void_tcausa problemas para algum compilador antigo (conforme apontado pelo link).
Jarod42
não é estritamente necessário escrever duas sobrecargas (substituir "precisa" por "pode" seria muito melhor)
idclev 463035818 07/02
@ idclev463035818 atualizado. Obrigado
NutCracker
11
@SSAnne updated
NutCracker
11
A definição do conceito não é precisa. você está atribuindo o resultado a um int para que o conceito sejatemplate<typename T> concept HasGetInt = requires (T& v) { {v.getInt()} -> std::convertible_to<int>; };
Hui
8

Você pode usar if constexprdo C ++ 17:

template<typename T>
void f(T& v){
    if constexpr(std::is_same_v<T, X>) { // Or better create trait has_getInt
        int i = v.getInt();// I want this to be called for X only
    }
    // ...
}

Antes, você precisará usar sobrecargas e SFINAE ou envio de tags.

Jarod42
fonte
if constexpré um recurso do C ++ 17.
Andrey Semashev
No entanto, isso funcionaria apenas para a classeX
NutCracker 07/02
Agora a pergunta é atualizada para apenas C ++ 11 / C ++ 14
NutCracker
@NutCracker: Não é bom atualizar a tag / pergunta e invalidar as respostas existentes ... (mesmo que seja bom alertar sobre isso).
Jarod42
Acabei de atualizar a tag ... o título da pergunta foi atualizado por OP
NutCracker 07/02
7

Mantenha-o simples e sobrecarregado. Funcionou desde pelo menos C ++ 98 ...

template<typename T>
void f(T& v)
{
    // do whatever
}

void f(X& v)
{
    int result = v.getInt();
}

Isso é suficiente se houver apenas um tipo com getIntfunção. Se houver mais, não é mais tão simples. Existem várias maneiras de fazer isso, aqui está uma:

struct PriorityA { };
struct PriorityB : PriorityA { };

template<typename T>
void f_impl(T& t, PriorityA)
{
    // generic version
}

// use expression SFINAE (-> decltype part)
// to enable/disable this overload
template<typename T>
auto f_impl(T& t, PriorityB) -> decltype(t.getInt(), void())
{
    t.getInt();
}

template<typename T>
void f(T& t)
{
    f_impl(t, PriorityB{ } ); // this will select PriorityB overload if it exists in overload set
                              // otherwise PriorityB gets sliced to PriorityA and calls generic version
}

Exemplo ao vivo com saída de diagnóstico.

jrok
fonte
11
Nesse caso, isso funcionaria, pois existe apenas uma sobrecarga (para X), mas, se houvesse mais tipos semelhantes de membros getIntno futuro, essa não é uma boa prática. Você provavelmente quer observar que
NutCracker
@NutCracker Fez isso.
jrok 07/02