Przeciążenie funkcji za pomocą szablonów

34

Próbuję zdefiniować funkcję za pomocą szablonów i chcę, aby nazwa typu była int lub anEnum (określony wyliczenie, które zdefiniowałem). Próbowałem następujące, ale nie powiodło się:

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

Staram się używać szablonów zamiast definiować dwie przeciążone funkcje. Wolałbym, aby funkcja była wywoływana w następujący sposób, bez konieczności rozważania typu przez programistę

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

Zasadniczo chcę, aby ta funkcja była szablonowana dla typów int i aNum. Szukałem tego, ale nie mogłem znaleźć odpowiedzi. Czego mi brakuje? Dziękuję Ci,

bg
źródło
Jeśli jest to dokładnie pojedynczy wyliczenie lub typ int, dlaczego po prostu nie napisać obu funkcji? Dlaczego potrzebujesz szablonu w takim przypadku?
Klaus
Co z innymi typami? Czy chcesz powrócić falsedo innych typów, czy nie chcesz tworzyć instancji funkcji dla innych typów?
frogatto
@frogatto Nie, wartość zwracana bool nie ma nic z typami.
bg
@Klaus Poprosiłem o naukę alternatyw. W oparciu o aktualne odpowiedzi postanowiłem po prostu zdefiniować obie funkcje.
bg

Odpowiedzi:

25

Oprócz odpowiedzi w języku innym niż C ++ 20, jeśli przypadkiem jesteś w stanie użyć C ++ 20 i jego conceptsfunkcji, proponuję ci następującą implementację:

#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;
}

Próbny

AKTUALIZACJA

Według komentarza @RichardSmith , oto bardziej skalowalne i wielokrotnego użytku podejście:

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;
}
Orzechówka
źródło
W konkretnym przypadku wymagającym, aby typ był jednym z dwóch określonych typów, coś takiego może działać lepiej: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
1
@RichardSmith Zaktualizowałem również moją odpowiedź. Uważam to za bardziej wielokrotnego użytku i skalowalne. Dzięki
NutCracker
21

Istnieje kilka sposobów na osiągnięcie tego. Wszystkie wymagają użycia type_traitsnagłówka. Możesz na przykład statycznie potwierdzić dane typy w treści funkcji.

Lub, jeśli trzeba rozważyć tę funkcję wśród innych przeciążeń, można zastosować technikę SFINAE.

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> {
}

Spowoduje to usunięcie funkcji z zestawu przeciążenia przed jej wywołaniem, jeśli typy nie będą pasować. Ale jeśli nie potrzebujesz tego zachowania, statyczne potwierdzenie pozwala na komunikat o błędzie bardziej przyjazny dla programisty.

StoryTeller - Unslander Monica
źródło
3

Co z tym rozwiązaniem? Kod z funkcją zostanie skompilowany, jeśli typ T spełni twoje wymagania. W przeciwnym razie statyczne potwierdzenie nie powiedzie się.

#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
źródło
1
Nie działa to dobrze z rozdzielczością przeciążenia, jeśli obecne są inne podpisy (np. Hipotetyczne isFunction(std::string_view)). Podpis będzie nadal prawidłowym dopasowaniem, ale tworzenie wystąpień powoduje błąd.
LF
Możesz zadeklarować bezużyteczne podpisy jako usunięte: bool isFunction (std :: string_view) = delete;
ixjxk
Mówię o dodatkowych przeciążeniach. W takim przypadku ta nieprawidłowa sygnatura może być dokładnie zgodna (np. W przypadku literałów łańcuchowych), blokując w ten sposób przeciążenie.
LF
0

Poprawiłem https://stackoverflow.com/a/60271100/12894563 odpowiedź. „Jeśli constexpr” może pomóc w tej sytuacji:

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
}

isFunction (1L) zawiedzie, ponieważ nie ma przeciążonej funkcji lub gałęzi „if constexpr”.

AKTUALIZACJA: Naprawiono brakujące

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

https://godbolt.org/z/eh4pVn

ixjxk
źródło
static_assert(false, ...)jest źle sformułowanym NDR, nawet go nie używam. Jeśli masz szczęście, twój kompilator od razu ci powie, podobnie jak Clang, godbolt.org/z/m_Gk9n
StoryTeller - Unslander Monica
Bardzo dziękuję za komentarz, popełniłem błąd. Naprawiono, godbolt.org/z/eh4pVn
ixjxk