Sobrecarregando uma Função Usando Modelos

34

Estou tentando definir uma função usando modelos e quero que o nome do tipo seja int ou anEnum (uma enumeração específica que eu havia definido). Eu tentei o seguinte, mas falhei:

template <int | anEnum T> // or <int T, anEnum T> or <int, anEnum T>
bool isFunction(const T &aVariable){}

O que estou tentando fazer é usar modelos, em vez de definir duas funções sobrecarregadas. Prefiro que a função seja chamada da seguinte forma, sem que o programador precise considerar o tipo

isFunction(aVariable) // and not isFunction<int> (aVariable) nor isFunction<anEnum> (aVariable)

Basicamente, eu quero que essa função seja modelada para os tipos int e aNum. Eu procurei por isso, mas não consegui encontrar a resposta. O que posso estar faltando? Obrigado,

bg
fonte
Se é exatamente uma enumeração única ou o tipo int, por que não simplesmente escrever as duas funções? Por que você precisa de um modelo nesse caso?
Klaus
E quanto a outros tipos? Deseja retornar falsepara outros tipos ou não instanciar a função para outros tipos.
frogatto 17/02
@frogatto Não, o valor de retorno bool não tem nada com os tipos.
bg
@Klaus Eu pedi para aprender alternativas. Com base nas respostas atuais, decidi simplesmente definir as duas funções.
bg

Respostas:

25

Além da resposta que não seja C ++ 20, se você puder, por acaso, usar o C ++ 20 e seu conceptsrecurso, sugiro a seguinte implementação:

#include <iostream>
#include <concepts>

enum class MyEnum {
    A,
    B,
    C
};

template <typename T>
concept IntegralOrEnum = std::same_as<MyEnum, T> || std::integral<T>;

template <IntegralOrEnum T>
bool isFunction(T const& aVariable) {
    return true;
}

int main() {
    isFunction(MyEnum::A);
    isFunction(3);
    isFunction("my_string"); // error
    return 0;
}

Demo

ATUALIZAR

De acordo com o comentário de @RichardSmith , aqui está uma abordagem mais escalável e reutilizável:

template <typename T, typename ...U>
concept one_of = (std::is_same_v<T, U> || ...);

template <one_of<int, MyEnum> T>
bool isFunction(T const& aVariable) {
    return true;
}
NutCracker
fonte
Para o caso específico de exigir que o tipo seja um de dois tipos específicos, algo como isso pode funcionar melhor:template<typename T, typename ...U> concept one_of = (std::is_same_v<T, U> || ...); template<one_of<int, MyEnum> T> bool isFunction(T const& aVariable) {
Richard Smith
11
@ RichardSmith Atualizei minha resposta com isso também. Acho isso mais reutilizável e escalável. Obrigado
NutCracker 19/02
21

Existem algumas maneiras de conseguir isso. Todos envolvem o uso do type_traitscabeçalho. Você pode afirmar estática nos tipos em questão no corpo da função, por exemplo.

Ou, se você precisar considerar essa função entre outras sobrecargas, uma técnica SFINAE pode ser empregada.

template<typename T>
auto isFunction(const T &aVariable) 
  -> std::enable_if_t<std::is_same<T, int>::value || std::is_same<T,anEnum>::value, bool> {
}

Isso removerá a função de um conjunto de sobrecargas antes de ser chamada se os tipos não corresponderem. Mas se você não precisar desse comportamento, uma asserção estática permitirá uma mensagem de erro mais amigável ao programador.

Contador de Histórias - Monica Sem Calúnia
fonte
3

E essa solução? Um código com a função será compilado se o tipo T atender às suas solicitações. Caso contrário, a asserção estática falhou.

#include <type_traits>
enum anEnum {
    //
};

template <typename T, bool defined = std::is_same<T, int>::value ||
                                     std::is_same<T, anEnum>::value>
bool isFunction(const T& aVariable)
{
    static_assert(defined, "Invalid specialization");

    bool result = false;
    // Put your code here
    return result;
}
ixjxk
fonte
11
Isso não funciona bem com a resolução de sobrecarga se houver outras assinaturas presentes (por exemplo, uma hipotética isFunction(std::string_view)). A assinatura ainda será uma correspondência válida, mas a instanciação causa erro.
LF
Você pode declarar assinaturas inúteis como excluídas: bool isFunction (std :: string_view) = delete;
ixjxk 18/02
Estou falando de sobrecargas adicionais. Nesse caso, essa assinatura inválida pode acabar sendo uma correspondência exata (por exemplo, para literais de string), bloqueando assim a sobrecarga.
LF
0

Melhorei a https://stackoverflow.com/a/60271100/12894563 resposta. 'Se constexpr' pode ajudar nessa situação:

template <typename T>
struct always_false : std::false_type {};

template <typename T>
bool isFunction(const T& aVariable)
{
    if constexpr(std::is_same_v<T, int> || std::is_same_v<T, anEnum>)
    {
        std::cout << "int\n";
        // put your code here
        return true;
    }
    else
    {
        static_assert(always_false<T>::value, "You should declare non-template function or write if constexpr branch for your type");
        return false;
    }
}

bool isFunction(std::string_view)
{
    std::cout << "std::string_view\n";
    return true;
}

int main()
{
    isFunction(std::string_view("1L"));
    isFunction(1);
    //isFunction(1L); // will produce an error message from static_assert
}

A função isFunction (1L) falhará porque não há função sobrecarregada ou ramificação 'if constexpr'.

UPDATE: Corrigida falta

template <typename T>
struct always_false : std::false_type {};

https://godbolt.org/z/eh4pVn

ixjxk
fonte
static_assert(false, ...)é uma notificação de falha na entrega mal formada, sem sequer ser usada. Se você tiver sorte, seu compilador lhe dirá imediatamente, como Clang, godbolt.org/z/m_Gk9n
StoryTeller - Unslander Monica
Muito obrigado pelo seu comentário, cometi um erro. Corrigido, godbolt.org/z/eh4pVn
ixjxk 18/02