Domanda Risolto Definire la specializzazione di un template per filtrare tipologie di tipi composti

DanyDollaro

Utente Electrum
14 Luglio 2018
148
41
58
138
Ultima modifica:
Il titolo potrebbe non sembrare molto chiaro, quello che sto cercando di fare è, avendo una funzione parametrica del tipo:

C++:
template<class _Ty>
static void test(_Ty Var)
{
    std::cout << "Generic type" << std::endl;
}

e delle specializzazioni di questo tipo:

C++:
template<>
static void test<int>(int Var)
{
    std::cout << "Int type" << std::endl;
}

template<>
static void test<float>(float Var)
{
    std::cout << "Float type" << std::endl;
}

Voglio definire delle specializzazioni di questo tipo:

C++:
// Specializzazione per dei vettori di interi
template<>
static void test<std::vector<int>>(std::vector<int> Var)
{
    std::cout << "Int vector type" << std::endl;
}

// Questa dovrebbe essere la specializzazione per i vettori di
// qualsiasi altro tipo, però non è valida per il compilatore
template<class _Ty>
static void test<std::vector<_Ty>>(std::vector<_Ty> Var)
{
    std::cout << "Generic vector type" << std::endl;
}

Per far si che la funzione test<std::vector<_Ty>> venga chiamata quando la tipologia del parametro sia un std::vector, ma il contenuto del vettore non sia conosciuto, e non riesco a trovare una soluzione a questo problema :\ , qualche aiuto?
 
Ultima modifica:
Wow! Che velocità! Grazie infinite St3ve per la risposta più che tempestiva :rofl:.
Messaggio unito automaticamente:

EDIT: Non so se creare un thread a parte, ma siccome la domanda appartiene al medesimo argomento la pongo qui.

Mentre, supponendo che al posto di un std::vector volessi far in modo di accettare anche altri container della STL a patto che abbiano la stessa interfaccia?
Ovvero, scrivere una funzione del genere che accetta std::vector e pure std::list con tipologie di elementi non specificati, sarebbe possibile?

Avevo in mente una cosa del genere però non funziona :boh:
C++:
#include <iostream>
#include <vector>
#include <list>

template<class _Ty>
void test(_Ty Var)
{
    std::cout << "Nope" << std::endl;
}

template<class _Ty, template<class> class _Container>
void test(_Container<_Ty> Var)
{
    Var.push_back(_Ty());
}

int main()
{
    std::vector<int> v;
    std::list<int> l;

    test<int, std::list<int>>(l); // <- specializzazione non trovata (genera un errore di compilazione)
    test(l); // <- mentre questo usa la versione principale ignorando la specializzazione
}
 
Puoi fare una cosa di questo tipo:
C++:
template <typename T, template <class...> class U> void test(U<T> lst) {
  lst.push_back(T());
}

template <class T> void test(T lst) {
  std::cout << "Nope" << std::endl;
}

Plausibilmente vuoi fare pass by reference se poi usi push_back.
 
  • Geniale
Reazioni: DanyDollaro
Ultima modifica:
belli i template
Uhm... no :asd:

Credimi che mi sono limitato a postare la soluzione per gli esseri umani, intuendo che Dany Dollaro voleva qualcosa di molto più semplice di ciò che ha effettivamente chiesto. Se avessi preso alla lettera la richiesta dell'utente avrei dovuto scrivere qualche schifo di accrocchio alieno fatto con la SFINAE. Roba che mi rifiuto di fare se non necessaria.

Googlando ho scoperto che in C++20 la situazione è migliorata parecchio e si può fare così:
C++:
template <typename T> void foo(T &obj) {
  if constexpr (requires(T &t, T::value_type u) { t.push_back(std::move(u)); }) {
    typename T::value_type v = {};
    obj.push_back(std::move(v));
  } else {
    std::cout << "Nope" << std::endl;
  }
}

int main() {
  std::vector<int> a;
  std::list<std::string> b;
  int c = 0;

  foo(a); // push_back
  foo(b); // push_back
  foo(c); // nope
}
Nonostante questa soluzione sia abbastanza pulita, ti assicuro che la mia opinione sul C++ resta che è un linguaggio orribile che la gente usa perché deve e non perché vuole... il problema è che per un motivo o per l'altro ci si ritrova ad usarlo abbastanza spesso visto che per molte cose non ha rivali.
 
si, vedo che ti piacciono :)
c++ mi difendo, e template ne ho usati tempo fa, ma devo dire che e' stato piu un esercizio che altro, avreo potuto fare senza, quindi "cargo cult". Devo rivedermeli bene, per capirne il reale (utile) utilizzo.
Concordo su c++, lo apprezzo per le stl, che uso volentieri, e diverse altre cosette. Lo uso su pc per qualsiasi tool che non riesco a fare in bash. Ma non sono fanatico, come certi miei ex colleghi che in orario di lavoro leggevano libri sui pattern e li infilavano dappertutto nel codice, al puro scopo di usarli. A mio avviso, non deve diventare come si dice una "sega mentale", io lo uso volentieri ma mantenendo codice piu semplice e pulito possibile. Poi, a ognuno le sue passioni :)
 
Ultima modifica:
Con il C++20 hai a disposizione anche i concept che ti permettono di vincolare i meta-parametri alle tipologie che rispecchiano i requisiti del concetto, di conseguenza avresti un qualcosa che a mio parere è mooooolto più pulito:
C++:
#include <iostream>
#include <string>
#include <vector>
#include <list>

template<class _Ty>
concept HasNeededInterface = requires{ _Ty::push_back; _Ty::value_type; };

template<class _Ty>
void foo(_Ty& Var)
{
    std::cout << "Nope" << std::endl;
}

template<HasNeededInterface _Ty>
void foo(_Ty& Container)
{
    Container.push_back(_Ty::value_type());
}

int main()
{
    std::vector<int> a;
    std::list<std::string> b;
    int c = 0;

    foo(a); // push_back
    foo(b); // push_back
    foo(c); // Nope
}

oppure:
C++:
#include <iostream>
#include <string>
#include <vector>
#include <list>

template<class _Ty>
concept HasNeededInterface = requires{ _Ty::push_back; _Ty::value_type; };

template<class _Ty>
void foo(_Ty& Var)
{
    // Questo è più simile a quello che hai fatto tu
    if constexpr (HasNeededInterface<_Ty>)
    {
        Var.push_back(_Ty::value_type());
    }
    else
    {
        std::cout << "Nope" << std::endl;
    }
}

int main()
{
    std::vector<int> a;
    std::list<std::string> b;
    int c = 0;

    foo(a); // push_back
    foo(b); // push_back
    foo(c); // Nope
}

Anche se probabilmente conoscevi già questa possibile soluzione dato che nel tuo esempio hai usato la parola chiave requires.
 
c++ mi difendo, e template ne ho usati tempo fa, ma devo dire che e' stato piu un esercizio che altro, avreo potuto fare senza, quindi "cargo cult". Devo rivedermeli bene, per capirne il reale (utile) utilizzo.
Concordo su c++, lo apprezzo per le stl, che uso volentieri, e diverse altre cosette.
Un meccanismo pulito per fornire compile-time generics è probabilmente più grave mancanza del C. Se usi C++ come C-replacement riguardati i template perché per creare strutture dati sono veramente una manna dal cielo... o meglio, creare strutture dati in C è comodo quanto una pigna in culo, quindi quando usi C++ ti sembra di stare in paradiso. Non l'hai specificato, ma scommetto che la parte delle stl che usi più spesso sono di gran lunga i containers: vector, string, unordered_map et simili.

Il problema è che C++ ce la mette proprio tutta per implementare ogni features possibile e immaginabile e i risultati sono quelli che sono: un linguaggio frankensteiniano che è impossibile usare in modo corretto. L'overloading è una feature già di per sé controversa che molti linguaggi (vedi go) scelgono volutamente di non implementare. L'overloading mischiato con i default parameters è intricatissimo da fare in modo veramente corretto (richiede std::forward e cose di questo tipo). La template specialization classica (quella che ha fatto e ha provato a fare Dani Dollaro nell'open post) è una feature ancora più controversa e che andrebbe usata solo quando non ti è possibile fare overloading; in particolare, può portare a risultati bizzarri che comunemente non ci si aspetta. E come se non bastasse alla fine ci siamo ridotti a fare una template specialization che non è nemmeno basata sui tipi, ma sul comportamento. Sulla carta ha l'aria di essere una cosa foga, ma di fatto è il peggio del peggio del peggio.

Dany Dollaro ha postato una poof of concept e io mi sono limitato a risolvere quella, ma è probabile che la soluzione migliore per risolvere il problema che sta affrontando consiste nel riprogettare il codice per non usare tutta questa roba.
 
  • Mi piace
Reazioni: DanyDollaro