Đối số mẫu mặc định cho các mẫu hàm


187

Tại sao các đối số mẫu mặc định chỉ được phép trên các mẫu lớp? Tại sao chúng ta không thể xác định loại mặc định trong mẫu hàm thành viên? Ví dụ:

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

Thay vào đó, C ++ buộc các đối số mẫu mặc định chỉ được phép trên một mẫu lớp.


8
+1 Đó thực sự là một câu hỏi khó.
AraK

1
Đối với ba câu trả lời được đăng đầu tiên, hãy xem xét ví dụ này: struct S { template <class R = int> R get_me_R() { return R(); } };Tham số mẫu không thể được suy ra từ ngữ cảnh.
AraK

3
Câu hỏi hay. 3 người đã trả lời rằng "không có ý nghĩa gì" và nói chung họ đều sai. Các tham số mẫu hàm không phải lúc nào cũng được khấu trừ từ các tham số gọi hàm. Ví dụ, nếu họ được cho phép tôi có thể viết template <int N = 1> int &increment(int &i) { i += N; return i; }, và sau đó increment(i);hoặc increment<2>(i);. Như nó là, tôi phải viết increment<1>(i);.
Steve Jessop

Trên thực tế, các ví dụ của tôi và AraK đều có thể được xử lý bằng cách quá tải. Tôi nghĩ rằng litb không thể, bởi vì tham số mẫu có thể được suy ra hoặc có thể được chỉ định.
Steve Jessop

3
@Steve: Dấu chấm phẩy bị thiếu thực sự là một toán tử EOL mới quá tải để bổ sung cho "Quá tải khoảng trắng C ++" của B. Stavtrup được xuất bản trên Tạp chí Lập trình hướng đối tượng, ngày 1 tháng 4 năm 1992. ( www2.research.att.com/~bs/ tờ giấy.html )

Câu trả lời:


148

Nó có ý nghĩa để đưa ra các đối số mẫu mặc định. Ví dụ: bạn có thể tạo một hàm sắp xếp:

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 giới thiệu chúng với C ++. Xem báo cáo lỗi này của Bjarne Stroustrup: Đối số mẫu mặc định cho các mẫu chức năng và những gì ông nói

Việc cấm các đối số mẫu mặc định cho các mẫu hàm là phần còn lại của thời gian mà các hàm tự do được coi là công dân hạng hai và yêu cầu tất cả các đối số mẫu phải được suy ra từ các đối số hàm thay vì được chỉ định.

Hạn chế nghiêm trọng làm chùn bước phong cách lập trình bằng cách làm cho các hàm độc lập không cần thiết khác với các hàm thành viên, do đó làm cho việc viết mã kiểu STL trở nên khó khăn hơn.


@Arman, liên kết báo cáo lỗi chứa các thay đổi được thực hiện cho bản nháp làm việc cho C ++ 0x và các cuộc thảo luận. Các đối số không được suy luận cũng không được chỉ định rõ ràng được lấy từ các đối số mặc định. GCC4.4 hỗ trợ các đối số mặc định cho các mẫu hàm trong chế độ C ++ 0x.
Julian Schaub - litb

4
Không có gì để làm với câu hỏi hoặc câu trả lời, nhưng Herb Sutter đã gọi tiêu chuẩn nâng cao C ++ 11 sau cuộc họp thứ bảy tuần trước. Tôi mới đọc nó hôm nay và cảm thấy muốn chia sẻ :) Herbutter.wordpress.com/2010/03/13/iêng
David Rodríguez - dribeas

và câu hỏi tiếp theo bắt buộc ... khi nào thì điều này được dự kiến ​​sẽ đưa nó vào các trình biên dịch khác :)
Jamie Cook

@ JohannesSchaub-litb Tôi gặp vấn đề tương tự: không có khả năng làm hỏng kiểu mặc định trong hàm mẫu. Tôi đã giải quyết bằng cách khởi tạo rõ ràng của hàm trên loại mặc định ( doubletrong trường hợp của tôi). Có lẽ nó không phải là "chung chung", nhưng có bất kỳ nhược điểm nào với cách làm này không? Cảm ơn.
JackOLogio

Đoạn mã sau không thể biên dịch, với các lỗi như error: invalid conversion from ‘int’ to ‘int*’, bất kỳ ý tưởng nào tại sao: `#include <mảng> #include <Thuật toán> #include <function> template <typename Iterator, typename Comp = std :: less <Iterator >> void my_sort ( Iterator ăn xin, Iterator end, Comp c = Comp ()) {std :: sort (ăn xin, kết thúc, c); } int main () {std :: mảng <int, 5> ar {5,2,21,7,4}; my_sort (ar.begin (), ar.end ()); } `
Luke Peterson

36

Để trích dẫn Mẫu C ++: Hướng dẫn hoàn chỉnh (trang 207):

Khi các mẫu ban đầu được thêm vào ngôn ngữ C ++, các đối số mẫu hàm rõ ràng không phải là một cấu trúc hợp lệ. Các đối số mẫu hàm luôn phải được khấu trừ từ biểu thức cuộc gọi. Kết quả là, dường như không có lý do thuyết phục nào để cho phép các đối số mẫu hàm mặc định bởi vì mặc định sẽ luôn bị ghi đè bởi giá trị được suy ra.


đơn giản và súc tích :)
InQusitive 17/12/14

17

Cho đến nay, tất cả các ví dụ phổ biến về các tham số mẫu mặc định cho các mẫu hàm có thể được thực hiện với tình trạng quá tải.

AraK:

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

có thể là:

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

Của riêng tôi:

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

có thể là:

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

có thể là:

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

Có thể là:

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

Mà tôi đã chứng minh với đoạn mã sau:

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

Đầu ra được in khớp với các nhận xét cho mỗi cuộc gọi đến f và cuộc gọi nhận xét không thể biên dịch như mong đợi.

Vì vậy, tôi nghi ngờ rằng các tham số mẫu mặc định "không cần thiết", nhưng có lẽ chỉ theo nghĩa tương tự rằng các đối số chức năng mặc định "không cần thiết". Như báo cáo lỗi của Stroustrup chỉ ra, việc bổ sung các tham số không suy diễn là quá muộn để bất kỳ ai nhận ra và / hoặc thực sự đánh giá cao rằng nó làm cho mặc định trở nên hữu ích. Vì vậy, tình hình hiện tại có hiệu lực dựa trên một phiên bản của các mẫu chức năng không bao giờ là tiêu chuẩn.


@Steve: Vậy là trứng chạy nhanh hơn gà? :) thú vị. Cảm ơn.
Arman

1
Có lẽ chỉ là một trong những điều đó. Quá trình tiêu chuẩn hóa C ++ diễn ra chậm một phần để mọi người có thời gian nhận ra khi một thay đổi tạo ra cơ hội hoặc khó khăn ở nơi khác trong tiêu chuẩn. Khó khăn hy vọng được bắt gặp bởi những người thực hiện dự thảo tiêu chuẩn khi họ đi cùng, khi họ phát hiện ra một mâu thuẫn hoặc mơ hồ. Cơ hội cho phép những thứ không được phép trước đây, dựa vào ai đó muốn viết mã nhận thấy rằng nó không còn cần phải bất hợp pháp nữa ...
Steve Jessop

2
Một lần nữa cho bạn : template<typename T = void> int SomeFunction();. Tham số mẫu ở đây không bao giờ được sử dụng và trên thực tế, hàm không bao giờ được gọi; nơi duy nhất nó được đề cập là trong một decltypehoặc sizeof. Tên cố tình khớp với tên của một chức năng khác nhưng thực tế đó là một mẫu có nghĩa là trình biên dịch sẽ thích chức năng miễn phí nếu nó tồn tại. Cả hai được sử dụng trong SFINAE để cung cấp hành vi mặc định trong đó thiếu định nghĩa hàm.
Tom

4

Trên Windows, với tất cả các phiên bản Visual Studio, bạn có thể chuyển đổi lỗi này ( C4519 ) thành cảnh báo hoặc vô hiệu hóa như vậy:

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

Xem thêm chi tiết tại đây .


1
Lưu ý rằng, mặc dù điều này không vô hiệu hóa thông báo "đối số mẫu mặc định chỉ được phép trên thông báo mẫu lớp", nhưng thực tế nó không làm cho quá trình khởi tạo mẫu sử dụng giá trị được cung cấp. Điều đó đòi hỏi VS2013 (hoặc bất kỳ trình biên dịch nào khác đã hoàn thành lỗi C ++ 11 226 "Đối số mẫu mặc định cho các mẫu hàm")
puetzk

1

Những gì tôi sử dụng là thủ thuật tiếp theo:

Hãy nói rằng bạn muốn có chức năng như thế này:

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

Bạn sẽ không được phép, nhưng tôi làm theo cách tiếp theo:

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

Vì vậy, theo cách này bạn có thể sử dụng nó như thế này:

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

Như chúng ta có thể thấy không cần thiết phải đặt tham số thứ hai một cách rõ ràng. Có lẽ nó sẽ hữu ích cho một ai đó.


Đây chắc chắn không phải là một câu trả lời.
Cún con

@deadmg - bạn có thể giải thích tại sao không? Chúng tôi không phải là tất cả các bậc thầy mẫu C ++. Cảm ơn.
Kev

Đây là một cách giải quyết khá gọn gàng nhưng nó sẽ không bao gồm tất cả các trường hợp bạn có thể muốn. Ví dụ, làm thế nào bạn sẽ áp dụng điều này cho một nhà xây dựng?
Tiberiu Savin

@TiberiuSavin - nếu tôi hiểu đúng về bạn, thì bạn có thể làm như thế này: template <typename E, typename ARR_E> worker <E, ARR_E> :: worker (ARR_E * parr) {parr_ = parr; }. Và sau đó sử dụng nó như thế này: worker <int> w2 (& my_array);
alariq
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.