Argumen templat default untuk templat fungsi

187

Mengapa argumen templat default hanya diizinkan pada templat kelas? Mengapa kita tidak bisa mendefinisikan tipe default di templat fungsi anggota? Sebagai contoh:

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

Sebaliknya, C ++ memaksa argumen templat default hanya diizinkan pada templat kelas.

Arman
sumber
8
+1 Itu benar-benar pertanyaan yang sulit.
AraK
1
Untuk tiga jawaban pertama yang diposting, pertimbangkan contoh ini: struct S { template <class R = int> R get_me_R() { return R(); } };Parameter template tidak dapat disimpulkan dari konteksnya.
AraK
3
Pertanyaan bagus. 3 orang sudah menjawab untuk mengatakan "tidak masuk akal", dan mereka semua salah secara umum. Parameter templat fungsi tidak selalu dapat dikurangkan dari parameter pemanggilan fungsi. Misalnya, jika diizinkan, saya bisa menulis template <int N = 1> int &increment(int &i) { i += N; return i; }, dan kemudian increment(i);atau increment<2>(i);. Karena itu, saya harus menulis increment<1>(i);.
Steve Jessop
Sebenarnya, contoh saya dan AraK dapat diatasi dengan kelebihan beban. litb tidak bisa, saya pikir, karena parameter template mungkin disimpulkan atau ditentukan.
Steve Jessop
3
@Steve: Titik koma yang hilang sebenarnya adalah kelebihan operator EOL baru untuk melengkapi B. Stavtrup "Overloading of C ++ Whitespace" yang diterbitkan dalam Journal of Object-Oriented Programming, 1 April 1992. ( www2.research.att.com/~bs/ papers.html )

Jawaban:

148

Masuk akal untuk memberikan argumen templat default. Misalnya, Anda dapat membuat fungsi pengurutan:

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 memperkenalkan mereka ke C ++. Lihat laporan cacat ini oleh Bjarne Stroustrup: Argumen Templat Default untuk Templat Fungsi dan apa yang dia katakan

Larangan argumen templat default untuk templat fungsi adalah sisa yang salah waktu ketika fungsi bebas diperlakukan sebagai warga negara kelas dua dan mengharuskan semua argumen templat disimpulkan dari argumen fungsi alih-alih ditentukan.

Pembatasan serius kram gaya pemrograman dengan tidak perlu membuat fungsi berdiri bebas berbeda dari fungsi anggota, sehingga membuatnya lebih sulit untuk menulis kode gaya STL.

Johannes Schaub - litb
sumber
@Arman, tautan laporan cacat berisi perubahan yang dibuat pada draft kerja untuk C ++ 0x dan diskusi. Argumen yang tidak dideduksi maupun ditentukan secara eksplisit diperoleh dari argumen default. GCC4.4 mendukung argumen default untuk templat fungsi dalam mode C ++ 0x.
Johannes Schaub - litb
4
Tidak ada hubungannya dengan pertanyaan atau jawaban, tetapi Herb Sutter memanggil standar C ++ 11 yang upcomming setelah pertemuan Sabtu lalu. Saya baru saja membacanya hari ini dan merasa ingin berbagi :) herbalutter.wordpress.com/2010/03/13/…
David Rodríguez - dribeas
dan pertanyaan tindak lanjut wajib ... kapan ini diharapkan bisa masuk ke kompiler lain :)
Jamie Cook
@ JohannesSchaub-litb Saya punya masalah yang sama: tidak ada kemungkinan untuk speficy tipe default dalam fungsi template. Saya telah memecahkan dengan instantiasi eksplisit dari fungsi pada tipe default ( doubledalam kasus saya). Mungkin ini bukan "umum", tetapi apakah ada kelemahan dengan praktik ini? Terima kasih.
JackOLantern
Kode berikut gagal dikompilasi, dengan kesalahan seperti error: invalid conversion from ‘int’ to ‘int*’, ada ide mengapa: `#include <array> #include <algorithm> #include <functional> template <typename Iterator, ketik nama Comp = std :: less <Iterator>> void my_sort ( Iterator mohon, Iterator akhir, Comp c = Comp ()) {std :: sort (mohon, end, c); } int main () {std :: array <int, 5> ar {5,2,21,7,4}; my_sort (ar.begin (), ar.end ()); ``
Luke Peterson
36

Mengutip Template C ++: Panduan Lengkap (halaman 207):

Ketika templat awalnya ditambahkan ke bahasa C ++, argumen templat fungsi eksplisit bukan konstruk yang valid. Argumen templat fungsi selalu harus dikurangkan dari ekspresi panggilan. Akibatnya, tampaknya tidak ada alasan kuat untuk mengizinkan argumen templat fungsi default karena default akan selalu ditimpa oleh nilai yang dideduksi.

James McNellis
sumber
sederhana dan ringkas :)
InQusitive
17

Sejauh ini, semua contoh parameter templat default yang disodorkan untuk templat fungsi dapat dilakukan dengan kelebihan beban.

AraK:

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

bisa jadi:

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

Saya sendiri:

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

bisa jadi:

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())

bisa jadi:

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);

Bisa jadi:

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

Yang saya buktikan dengan kode berikut:

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

Output yang dicetak cocok dengan komentar untuk setiap panggilan ke f, dan panggilan yang dikomentari gagal untuk mengkompilasi seperti yang diharapkan.

Jadi saya menduga bahwa parameter templat default "tidak diperlukan", tetapi mungkin hanya dalam arti yang sama bahwa argumen fungsi default "tidak diperlukan". Seperti yang ditunjukkan oleh laporan cacat Stroustrup, penambahan parameter yang tidak dideduksikan sudah terlambat bagi siapa pun untuk menyadari dan / atau benar-benar menghargai bahwa itu membuat default berguna. Jadi situasi saat ini berlaku berdasarkan versi templat fungsi yang tidak pernah standar.

Steve Jessop
sumber
@Steve: Jadi telurnya berjalan lebih cepat dari ayam? :) menarik. Terima kasih.
Arman
1
Mungkin hanya salah satu dari hal-hal itu. Proses standardisasi C ++ berjalan lambat sebagian sehingga orang punya waktu untuk menyadari kapan perubahan menciptakan peluang atau kesulitan di tempat lain dalam standar. Kesulitan-kesulitan mudah-mudahan tertangkap oleh orang-orang yang menerapkan rancangan standar saat mereka berjalan, ketika mereka melihat kontradiksi atau ambiguitas. Peluang untuk mengizinkan hal-hal yang tidak diizinkan sebelumnya, bergantung pada seseorang yang ingin menulis kode memperhatikan bahwa itu tidak perlu lagi ilegal ...
Steve Jessop
2
Satu lagi untuk Anda: template<typename T = void> int SomeFunction();. Parameter templat di sini tidak pernah digunakan, dan sebenarnya fungsinya tidak pernah dipanggil; satu-satunya tempat yang dimaksud adalah dalam decltypeatau sizeof. Nama sengaja cocok dengan nama fungsi lain tetapi kenyataannya itu adalah template berarti kompiler akan lebih memilih fungsi gratis jika ada. Keduanya digunakan di SFINAE untuk memberikan perilaku default di mana definisi fungsi tidak ada.
Tom
4

Pada Windows, dengan semua versi Visual Studio Anda dapat mengubah kesalahan ini ( C4519 ) menjadi peringatan atau menonaktifkannya seperti ini:

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

Lihat lebih detail di sini .

Adi Shavit
sumber
1
Perhatikan bahwa, sementara ini menonaktifkan pesan "argumen templat default hanya diizinkan pada templat kelas", itu tidak benar-benar membuat proses instantiasi templat menggunakan nilai yang disediakan. Itu membutuhkan VS2013 (atau kompiler lain yang telah menyelesaikan cacat C ++ 11 226 "Argumen templat default untuk templat fungsi")
puetzk
1

Apa yang saya gunakan adalah trik selanjutnya:

Katakanlah Anda ingin memiliki fungsi seperti ini:

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

Anda tidak akan diizinkan, tetapi saya lakukan selanjutnya:

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

Jadi dengan cara ini Anda dapat menggunakannya seperti ini:

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

Seperti yang dapat kita lihat, tidak perlu secara eksplisit mengatur parameter kedua. Mungkin itu akan bermanfaat bagi seseorang.

alariq
sumber
Ini jelas bukan jawaban.
Anak anjing
@deadmg - dapatkah Anda menjelaskan alasannya? Kita tidak semua guru template C ++. Terima kasih.
Kev
Ini adalah solusi yang cukup rapi tetapi tidak akan mencakup semua kasus yang Anda inginkan. Misalnya bagaimana Anda menerapkan ini pada konstruktor?
Tiberiu Savin
@TiberiuSavin - jika saya mengerti Anda dengan benar, maka Anda dapat melakukan seperti ini: template <typename E, typename ARR_E> worker <E, ARR_E> :: worker (ARR_E * parr) {parr_ = parr; }. Dan kemudian gunakan seperti ini: pekerja <int> w2 (& my_array);
alariq