2015-01-07 19 views
13

Tôi đang thử nghiệm với các hàm constexpr trong C++ 14. Các mã sau đây, mà sẽ tính toán các công trình thừa như mong đợi:C++ 14: suy ra (tự động) các kiểu trả về từ constexpr với các biểu thức bậc ba

template <typename T> 
constexpr auto fact(T a) { 
    if(a==1) 
     return 1; 
    return a*fact(a-1); 
} 

int main(void) { 
    static_assert(fact(3)==6, "fact doesn't work"); 
} 

khi nó được biên soạn như sau với kêu vang:

> clang++ --version 
clang version 3.5.0 (tags/RELEASE_350/final) 
Target: x86_64-unknown-linux-gnu 
Thread model: posix 
> clang++ -std=c++14 constexpr.cpp 

Tuy nhiên, khi tôi thay đổi fact định nghĩa để sử dụng ? hành ternary:

template <typename T> 
constexpr auto fact(T a) { 
    return a==1 ? 1 : a*fact(a-1); 
} 

tôi nhận được lỗi biên dịch sau:

> clang++ -std=c++14 constexpr.cpp 
constexpr.cpp:12:31: fatal error: recursive template instantiation exceeded maximum depth of 
     256 
    return a==T(1) ? T(1) : a*fact(a-1); 
     ... snip ... 
constexpr.cpp:16:19: note: in instantiation of function template specialization 'fact<int>' 
     requested here 
    static_assert(fact(3)==6, "fact doesn't work"); 

Vấn đề là cố định nếu tôi một cách rõ ràng nêu rõ kiểu trả về T (thay vì sử dụng ô tô để suy ra kiểu trả về)

template <typename T> 
constexpr T fact(T a) { 
    return a==1 ? 1 : a*fact(a-1); 
} 

Nếu tôi loại bỏ các tham số mẫu, mô hình được lặp lại (phiên bản ternary thất bại, và phiên bản if làm việc)

// this works just fine 
constexpr auto fact(int a) { 
    if(a==1) 
     return 1; 
    return a*fact(a-1); 
} 

trong khi điều này không

constexpr auto fact(int a) { 
    return a==1 ? 1 : a*fact(a-1); 
} 

với lỗi sau

> clang++ -std=c++14 constexpr.cpp 
constexpr.cpp:16:25: error: function 'fact' with deduced return type cannot be used before it 
     is defined 
    return a==1 ? 1 : a*fact(a-1); 
         ^
constexpr.cpp:15:16: note: 'fact' declared here 
constexpr auto fact(int a) { 
      ^
constexpr.cpp:20:26: error: invalid operands to binary expression ('void' and 'int') 
    static_assert(fact(3)==6, "fact doesn't work"); 
        ~~~~~~~^ ~ 
2 errors generated. 

Điều gì đang xảy ra ở đây?

Trả lời

11

Loại kết quả từ việc đánh giá biểu thức bậc ba là common type of its second and third arguments.

Khi trình biên dịch suy ra kiểu trả về, bạn buộc nó đánh giá cả hai đối số này vào biểu thức bậc ba. Điều này có nghĩa rằng đệ quy không kết thúc ngay cả khi điều kiện kết thúc đạt được, bởi vì khi a==1, để tìm ra kiểu trả về của fact(0) trình biên dịch phải tiếp tục đánh giá các cuộc gọi đệ quy tiếp theo đến fact và việc đệ quy vô tận xảy ra sau đó.

Bằng cách ghi rõ loại trả lại, fact(0) không cần đánh giá khi a==1 và đệ quy có thể chấm dứt.


Đối với các trường hợp với hai return báo cáo, các khoản tiêu chuẩn có liên quan là —

(từ N4296) §7.1.6.4/9[dcl.spec.auto]

If a function with a declared return type that contains a placeholder type has multiple return statements, the return type is deduced for each return statement. If the type deduced is not the same in each deduction, the program is ill-formed.

Trong ví dụ của bạn, trong cuộc gọi đến fact<int>(1), loại trả về được khấu trừ từ câu lệnh return đầu tiên là int, do đó, kiểu trả về là fact<int>(0) trong câu lệnh thứ hai return không được là gì cả ngoài số int. Điều này có nghĩa là trình biên dịch không cần đánh giá phần thân của fact<int>(0) và đệ quy có thể chấm dứt.

Thật vậy, nếu bạn buộc đánh giá các cuộc gọi đến fact trong báo cáo thứ hai return là tốt, ví dụ bằng cách thay đổi ví dụ đầu tiên để T là một tổ chức phi kiểu mẫu lập luận

template <unsigned T> 
constexpr auto fact() { 
    if(T==1) 
     return 1; 
    return T*fact<T-1>(); 
} 

kêu vang làm thất bại với lỗi

fatal error: recursive template instantiation exceeded maximum depth of 256

Live demo

+0

các phiên bản nếu có hai báo cáo trở lại. Liệu trình biên dịch sử dụng/tiêu chuẩn xác định một số loại evaulation lười biếng, nơi mà nó chỉ xem xét các báo cáo trở lại trong khối nếu khi một == 1? Nếu không, chúng tôi sẽ có cùng một vấn đề với cả hai phiên bản. – bcumming

+0

Cảm ơn @Praetorian. Tôi có thể tự tìm kiếm (mặc dù tôi không phải là một luật sư ngôn ngữ). – bcumming

+0

@Praetorian Nó không phải là trái ngược với những gì bạn tuyên bố trước đó. Kiểu trả về được suy ra cho mỗi câu lệnh return, nhưng một khi nó được suy ra cho câu lệnh trả về * any *, kiểu trả về được biết và có thể được sử dụng như một phần của việc khấu trừ các câu lệnh trả về sau. Lý do ví dụ của bạn với 'template ' không thành công là vì kiểu trả về 'thực tế là ' được biết không nói gì về kiểu trả về 'thực tế ', nhưng nó là 'thực tế ' gọi 'thực tế 'trong mã OP . – hvd

Các vấn đề liên quan