Domyślne argumenty szablonu dla szablonów funkcji

187

Dlaczego domyślne argumenty szablonów są dozwolone tylko w szablonach klas? Dlaczego nie możemy zdefiniować domyślnego typu w szablonie funkcji członka? Na przykład:

struct mycclass {
  template<class T=int>
  void mymember(T* vec) {
    // ...
  }
};

Zamiast tego C ++ wymusza, aby domyślne argumenty szablonu były dozwolone tylko w szablonie klasy.

Arman
źródło
8
+1 To naprawdę trudne pytanie.
AraK
1
W przypadku pierwszych trzech opublikowanych odpowiedzi rozważ ten przykład: struct S { template <class R = int> R get_me_R() { return R(); } };parametru szablonu nie można wywnioskować z kontekstu.
AraK
3
Dobre pytanie. 3 osoby już odpowiedziały, że „to nie ma sensu” i ogólnie wszystkie są w błędzie. Parametry szablonu funkcji nie zawsze można odliczyć od parametrów wywołania funkcji. Na przykład, gdyby im pozwolono, mógłbym pisać template <int N = 1> int &increment(int &i) { i += N; return i; }, a następnie increment(i);lub increment<2>(i);. W tej chwili muszę pisać increment<1>(i);.
Steve Jessop,
W rzeczywistości moje i AraK przykłady mogą być rozwiązane przez przeciążenie. litb nie może, jak sądzę, ponieważ parametr szablonu może być wyprowadzona lub może być określony.
Steve Jessop,
3
@Steve: Brakujący średnik jest tak naprawdę nowym przeciążeniem operatora EOL, uzupełniającym „Przeciążenie białych znaków C ++” B. Stavtrupa opublikowanym w Journal of Object-Oriented Programming, 1 kwietnia 1992 r. ( Www2.research.att.com/~bs/ papers.html )

Odpowiedzi:

148

Podanie domyślnych argumentów szablonu ma sens. Na przykład możesz utworzyć funkcję sortowania:

template<typename Iterator, 
         typename Comp = std::less<
            typename std::iterator_traits<Iterator>::value_type> >
void sort(Iterator beg, Iterator end, Comp c = Comp()) {
  ...
}

C ++ 0x wprowadza je do C ++. Zobacz ten raport wady Bjarne Stroustrup: Argumenty szablonów domyślnych dla szablonów funkcji i jego wypowiedzi

Zakaz domyślnych argumentów szablonów dla szablonów funkcji jest błędnie zapomnianym czasem, gdy funkcje wolnostojące były traktowane jak obywatele drugiej klasy i wymagały, aby wszystkie argumenty szablonu były wywnioskowane z argumentów funkcji, a nie określone.

Ograniczenie to poważnie ogranicza styl programowania, niepotrzebnie różnicując funkcje wolnostojące od funkcji składowych, co utrudnia pisanie kodu w stylu STL.

Johannes Schaub - litb
źródło
@Arman, link do raportu o defektach zawiera zmiany wprowadzone w roboczej wersji C ++ 0x i dyskusje. Argumenty, które nie zostały wydedukowane ani wyraźnie określone, są uzyskiwane z domyślnych argumentów. GCC4.4 obsługuje domyślne argumenty dla szablonów funkcji w trybie C ++ 0x.
Johannes Schaub - litb
4
Nie ma to nic wspólnego z pytaniem lub odpowiedzią, ale Herb Sutter nazwał nadchodzący standard C ++ 11 po ostatnim sobotnim spotkaniu. Właśnie przeczytałem go dzisiaj i mam ochotę podzielić się :) herbutter.wordpress.com/2010/03/13/…
David Rodríguez - dribeas
i obowiązkowe pytanie uzupełniające ... kiedy ma się to znaleźć w innych kompilatorach :)
Jamie Cook
@ JohannesSchaub-litb Miałem ten sam problem: brak możliwości speficyzowania domyślnego typu w funkcji szablonu. Rozwiązałem problem poprzez jawne utworzenie funkcji domyślnego typu ( doublew moim przypadku). Być może nie jest to „ogólne”, ale czy ta praktyka ma jakąś wadę? Dzięki.
JackOLantern
Poniższy kod się nie kompiluje, z błędami podobnymi do error: invalid conversion from ‘int’ to ‘int*’żadnego, dlaczego: Iterator beg, Iterator end, Comp c = Comp ()) {std :: sort (beg, end, c); } int main () {std :: array <int, 5> ar {5,2,21,7,4}; my_sort (ar.begin (), ar.end ()); } `
Luke Peterson
36

Cytować szablony C ++: Kompletny przewodnik (strona 207):

Kiedy szablony zostały pierwotnie dodane do języka C ++, jawne argumenty szablonu funkcji nie były prawidłową konstrukcją. Argumenty szablonu funkcji zawsze trzeba było wydedukować z wyrażenia wywołania. W rezultacie wydaje się, że nie ma istotnego powodu, aby zezwolić na domyślne argumenty szablonu funkcji, ponieważ domyślna wartość zawsze byłaby nadpisywana przez wartość wywnioskowaną.

James McNellis
źródło
proste i zwięzłe :)
InQusitive
17

Do tej pory wszystkie podane przykłady domyślnych parametrów szablonów dla szablonów funkcji można wykonać z przeciążeniem.

ARAK:

struct S { 
    template <class R = int> R get_me_R() { return R(); } 
};

możliwe:

struct S {
    template <class R> R get_me_R() { return R(); } 
    int get_me_R() { return int(); }
};

Mój własny:

template <int N = 1> int &increment(int &i) { i += N; return i; }

możliwe:

template <int N> int &increment(int &i) { i += N; return i; }
int &increment(int &i) { return increment<1>(i); }

litb:

template<typename Iterator, typename Comp = std::less<Iterator> >
void sort(Iterator beg, Iterator end, Comp c = Comp())

możliwe:

template<typename Iterator>
void sort(Iterator beg, Iterator end, std::less<Iterator> c = std::less<Iterator>())

template<typename Iterator, typename Comp >
void sort(Iterator beg, Iterator end, Comp c = Comp())

Stroustrup:

template <class T, class U = double>
void f(T t = 0, U u = 0);

Możliwe:

template <typename S, typename T> void f(S s = 0, T t = 0);
template <typename S> void f(S s = 0, double t = 0);

Co udowodniłem za pomocą następującego kodu:

#include <iostream>
#include <string>
#include <sstream>
#include <ctype.h>

template <typename T> T prettify(T t) { return t; }
std::string prettify(char c) { 
    std::stringstream ss;
    if (isprint((unsigned char)c)) {
        ss << "'" << c << "'";
    } else {
        ss << (int)c;
    }
    return ss.str();
}

template <typename S, typename T> void g(S s, T t){
    std::cout << "f<" << typeid(S).name() << "," << typeid(T).name()
        << ">(" << s << "," << prettify(t) << ")\n";
}


template <typename S, typename T> void f(S s = 0, T t = 0){
    g<S,T>(s,t);
}

template <typename S> void f(S s = 0, double t = 0) {
    g<S,double>(s, t);
}

int main() {
        f(1, 'c');         // f<int,char>(1,'c')
        f(1);              // f<int,double>(1,0)
//        f();               // error: T cannot be deduced
        f<int>();          // f<int,double>(0,0)
        f<int,char>();     // f<int,char>(0,0)
}

Wydrukowany wynik odpowiada komentarzom dla każdego wywołania f, a wykomentowane wywołanie nie kompiluje się zgodnie z oczekiwaniami.

Podejrzewam więc, że domyślne parametry szablonu „nie są potrzebne”, ale prawdopodobnie tylko w tym samym sensie, że domyślne argumenty funkcji „nie są potrzebne”. Jak wskazuje raport defektów Stroustrupa, dodanie niededykowanych parametrów było zbyt późno, aby ktokolwiek zdał sobie sprawę i / lub naprawdę docenił, że sprawiło, że domyślne były przydatne. Obecna sytuacja jest więc oparta na wersji szablonów funkcji, która nigdy nie była standardowa.

Steve Jessop
źródło
@ Steve: Więc jajko biegło szybciej niż kurczak? :) ciekawe. Dzięki.
Arman,
1
Prawdopodobnie tylko jedna z tych rzeczy. Proces standaryzacji w C ++ przebiega częściowo powoli, aby ludzie mieli czas na zrozumienie, kiedy zmiana stwarza szanse lub trudności w innym miejscu standardu. Mamy nadzieję, że trudności napotykają ludzie wdrażający projekt standardu, gdy zauważają sprzeczność lub dwuznaczność. Możliwości zezwalania na rzeczy, które wcześniej nie były dozwolone, polegaj na kimś, kto chce napisać kod, zauważając, że nie musi to już być nielegalne ...
Steve Jessop
2
Jeden dla Ciebie: template<typename T = void> int SomeFunction();. Parametr szablonu tutaj nigdy nie jest używany, a funkcja nigdy nie jest wywoływana; jedyne miejsce, do którego się to odnosi, to w decltypelub sizeof. Nazwa celowo pasuje do nazwy innej funkcji, ale fakt, że jest szablonem, oznacza, że ​​kompilator będzie preferował funkcję bezpłatną, jeśli istnieje. Oba są używane w SFINAE, aby zapewnić domyślne zachowanie w przypadku braku definicji funkcji.
Tom
4

W systemie Windows we wszystkich wersjach programu Visual Studio można przekonwertować ten błąd ( C4519 ) na ostrzeżenie lub wyłączyć go w następujący sposób:

#ifdef  _MSC_VER
#pragma warning(1 : 4519) // convert error C4519 to warning
// #pragma warning(disable : 4519) // disable error C4519
#endif

Zobacz więcej szczegółów tutaj .

Adi Shavit
źródło
1
Należy zauważyć, że chociaż powoduje to wyłączenie komunikatu „domyślne argumenty szablonu są dozwolone tylko w szablonie klasy”, w rzeczywistości proces tworzenia szablonu nie używa podanej wartości. Wymaga to VS2013 (lub innego kompilatora, który ukończył defekt C ++ 11 226 „Domyślne argumenty szablonów dla szablonów funkcji”)
puetzk
1

Używam następnej sztuczki:

Powiedzmy, że chcesz mieć taką funkcję:

template <typename E, typename ARR_E = MyArray_t<E> > void doStuff(ARR_E array)
{
    E one(1);
    array.add( one );
}

Nie będziesz miał pozwolenia, ale robię to w następujący sposób:

template <typename T>
struct MyArray_t {
void add(T i) 
{
    // ...
}
};

template <typename E, typename ARR_E = MyArray_t<E> >
class worker {
public:
    /*static - as you wish */ ARR_E* parr_;
    void doStuff(); /* do not make this one static also, MSVC complains */
};

template <typename E, typename ARR_E>
void worker<E, ARR_E>::doStuff()
{
    E one(1);
    parr_->add( one );
}

W ten sposób możesz użyć go w następujący sposób:

MyArray_t<int> my_array;
worker<int> w;
w.parr_ = &arr;
w.doStuff();

Jak widzimy, nie ma potrzeby jawnego ustawiania drugiego parametru. Może przyda się komuś.

alariq
źródło
To zdecydowanie nie jest odpowiedź.
Szczeniak
@deadmg - czy możesz wyjaśnić, dlaczego? Nie wszyscy jesteśmy guru szablon C ++. Dzięki.
Kev
Jest to obejście, które jest całkiem fajne, ale nie obejmie wszystkich potrzebnych przypadków. Na przykład, jak zastosowałbyś to do konstruktora?
Tiberiu Savin,
@TiberiuSavin - jeśli dobrze cię zrozumiałem, możesz to zrobić w następujący sposób: szablon <nazwa_typu E, nazwa_typu ARR_E> pracownik <E, ARR_E> :: pracownik (ARR_E * parr) {parr_ = parr; }. A następnie użyj tego w następujący sposób: worker <int> w2 (& my_array);
alariq