2013-04-17 43 views
13

Đây là một số loại theo dõi cho this topic và đề cập đến một phần nhỏ của nó. Như với chủ đề trước, chúng ta hãy xem xét rằng trình biên dịch của chúng tôi có các hàm constexpr cho std::initializer_liststd::array. Bây giờ, chúng ta hãy đi thẳng vào vấn đề.Lẫn lộn về các biểu thức không đổi

This works:

#include <array> 
#include <initializer_list> 

int main() 
{ 
    constexpr std::array<int, 3> a = {{ 1, 2, 3 }}; 
    constexpr int a0 = a[0]; 
    constexpr int a1 = a[1]; 
    constexpr int a2 = a[2]; 
    constexpr std::initializer_list<int> b = { a0, a1, a2 }; 

    return 0; 
} 

This does not:

#include <array> 
#include <initializer_list> 

int main() 
{ 
    constexpr std::array<int, 3> a = {{ 1, 2, 3 }}; 
    constexpr std::initializer_list<int> b = { a[0], a[1], a[2] }; 

    return 0; 
} 

Nó bị treo với lỗi này:

error: 'const std::initializer_list<int>{((const int*)(&<anonymous>)), 3u}' is not a constant expression 

Mặc dù tôi đọc một số giấy tờ về constexpr và thường xuyên biểu trong khi đó, hành vi này vẫn không có ý nghĩa gì đối với tôi. Làm thế nào đến ví dụ đầu tiên được coi là một biểu thức không đổi hợp lệ và không phải là biểu thức thứ hai? Tôi sẽ hoan nghênh mọi lời giải thích để tôi có thể yên nghỉ sau đó.

LƯU Ý: Tôi sẽ chính xác ngay lập tức, Clang sẽ không thể biên dịch đoạn đầu tiên vì nó không thực hiện các bổ sung thư viện constexpr được lên kế hoạch cho C++ 14. Tôi đã sử dụng GCC 4.7.

EDIT: Ok, ở đây có các ví dụ lớn để hiển thị những gì bị từ chối và những gì không phải là:

#include <array> 
#include <initializer_list> 

constexpr int foo = 42; 
constexpr int bar() { return foo; } 
struct eggs { int a, b; }; 

int main() 
{ 
    constexpr std::array<int, 3> a = {{ 1, 2, 3 }}; 
    constexpr int a0 = a[0]; 
    constexpr int a1 = a[1]; 
    constexpr int a2 = a[2]; 

    // From Xeo and Andy tests 
    constexpr std::array<int, 1> a = { bar() }; // OK 
    constexpr std::array<int, 3> b = {{ a[0], a[1], a[2] }}; // OK 
    std::initializer_list<int> b = { a[0], a[1], a[2] }; // OK 
    constexpr std::initializer_list<int> b = { a0, a1, a2 }; // OK 
    constexpr std::initializer_list<int> b = { foo }; // OK 
    constexpr std::initializer_list<int> c = { bar() }; // ERROR 
    constexpr std::initializer_list<int> b = { a[0], a[1], a[2] }; // ERROR 

    // From Matheus Izvekov and Daniel Krügler 
    constexpr eggs good = { 1, 2 }; // OK 
    constexpr std::initializer_list<eggs> bad = { { 1, 2 }, { 3, 4 } }; // ERROR 
    constexpr std::initializer_list<eggs> bad2 = { good, good }; // ERROR 

    return 0; 
} 
+0

Làm thế nào về "GCC có lỗi"? :) (Không nói nó có một, chỉ là một khả năng.) Và thực sự, bạn sẽ có thể kiểm tra điều này mà không cần bổ sung 'constexpr' bằng cách viết các chất tương tự của riêng bạn. Ngoài ra, còn 'constexpr std :: mảng b = {{a [0], a [1], a [2]}};'? – Xeo

+1

Có thể [this] (http://ideone.com/56iP0Y) giúp thu hẹp sự cố –

+0

@Xeo Bất cứ điều gì tôi làm với mảng có vẻ hoạt động tốt (bao gồm ví dụ của bạn và Andy chỉ có 'std :: arrays' thay thế của 'std :: initializer_list'). Dường như vấn đề chỉ xảy ra với 'std :: initializer_list' tại thời gian biên dịch. Tôi không quản lý để tái tạo nó mà không có 'constexpr' hoặc với' std :: array'. – Morwenn

Trả lời

1

tôi đã tìm ra những gì đang xảy ra ở đây:

constexpr std::initializer_list<int> b = { a[0], a[1], a[2] }; 

a[0] của nhập const int& chuyển hoàn toàn sang loại tạm thời const int. Sau đó, g ++ chuyển đổi nó thành const int* để chuyển sang hàm tạo riêng initializer_list. Trong bước cuối cùng, địa chỉ tạm thời, vì vậy nó không phải là một biểu thức liên tục.

Sự cố là chuyển đổi ngầm thành const int. Ví dụ:

constexpr int v = 1; 
const int& r = v; // ok 
constexpr int& r1 = v; // error: invalid initialization of reference of 
         // type ‘int&’ from expression of type ‘const int’ 

Hành vi tương tự là trong tiếng kêu.

Tôi nghĩ rằng chuyển đổi này là hợp pháp, không có gì nói ngược lại.

Về const int& để const int chuyển đổi, [expr] đoạn 5:

If an expression initially has the type “reference to T” , the type is adjusted to T prior to any further analysis. The expression designates the object or function denoted by the reference, and the expression is an lvalue or an xvalue, depending on the expression.

Kết quả của a[0] biểu là Xvalue tạm thời loại const int trong trường hợp đó.

Về chuyển đổi tiềm ẩn trong constexpr initializer, [dcl.constexpr] đoạn 9:

... Each implicit conversion used in converting the initializer expressions and each constructor call used for the initialization shall be one of those allowed in a constant expression.

Về lấy địa chỉ tạm, [expr.const] đoạn 2:

...an invocation of a constexpr function with arguments that, when substituted by function invocation substitution, do not produce a constant expression; [ Example:

constexpr const int* addr(const int& ir) { return &ir; } // OK 
static const int x = 5; 
constexpr const int* xp = addr(x); // OK: (const int*)&(const int&)x is an 
            // address contant expression 
constexpr const int* tp = addr(5); // error, initializer for constexpr variable 
            // not a constant expression; 
            // (const int*)&(const int&)5 is not a 
            // constant expression because it takes 
            // the address of a temporary 

— end example ]

+0

Ctor riêng không được gọi với các yếu tố bạn vượt qua , nó được gọi với một con trỏ tới một mảng * được khởi tạo từ các phần tử bạn vượt qua, và một kích thước hoặc một con trỏ đến cuối. – Xeo

+0

Tôi thực sự đã gửi [báo cáo lỗi] (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=56991) đến GCC. Một trong những người quản lý để có g + + từ chối một ví dụ khác mà không liên quan đến bất kỳ tài liệu tham khảo. – Morwenn

+0

Tôi chỉ tập trung vào 'const int &' để 'const int' chuyển đổi, gây ra hành vi như vậy và có vẻ hợp pháp. Ví dụ đầu tiên cho thấy chuyển đổi trong phần thô. Một phần về ctor là để giải thích thông báo lỗi chỉ. –

1

Ví dụ của bạn đều bị hỏng.

tl/dr: Trình khởi tạo không liên tục vì nó đề cập đến tạm thời khác nhau mỗi khi hàm được đánh giá.

Việc kê khai:

constexpr std::initializer_list<int> b = { a0, a1, a2 }; 

tạo ra một mảng tạm thời loại const int [3] (C++ 11 [dcl.init.list] p5), sau đó liên kết với các đối tượng std::initializer_list<int> đó tạm thời như thể bởi ràng buộc một tham chiếu đến nó (C++ 11 [dcl.init.list] p6).

Bây giờ, 11 C++ [expr.const] p4,

For a literal constant expression of array or class type, each subobject [...] shall have been initialized by a constant expression. [...] An address constant expression [...] evaluates to the address of an object with static storage duration.

Kể từ b có thời gian lưu trữ tự động, khi đối tượng std::initializer_list<int> liên kết với các const int [3] tạm thời, tạm thời cũng được đưa ra tự động thời gian lưu trữ, do đó, việc khởi tạo bkhông phải là một biểu thức liên tục vì nó đề cập đến địa chỉ của một đối tượng không có thời lượng lưu trữ tĩnh. Vì vậy, tuyên bố của b là không đúng định dạng.

Tại sao GCC chấp nhận một số các constexprstd::initializer_list đối tượng

Trong trường hợp khởi tạo là phù hợp tầm thường, GCC (và Clang) thúc đẩy các mảng để lưu trữ toàn cầu chứ không phải là tạo ra một tạm thời mới mỗi lần xung quanh. Tuy nhiên, trong GCC, kỹ thuật triển khai này rò rỉ thông qua ngữ nghĩa ngôn ngữ - GCC xử lý mảng có thời gian lưu trữ tĩnh và chấp nhận khởi tạo (như một phần mở rộng ngẫu nhiên hoặc cố tình với các quy tắc C++ 11).

Cách giải quyết (chỉ Clang)

Bạn có thể làm ví dụ của bạn hợp lệ bằng cách cho các std::initializer_list<int> đối tượng thời gian lưu trữ tĩnh:

static constexpr std::initializer_list<int> b = { a0, a1, a2 }; 

này lần lượt mang đến cho thời gian lưu trữ tĩnh đến các mảng tạm thời, mà làm cho việc khởi tạo là một biểu thức không đổi.

Sử dụng Clang và libC++ (với constexpr thêm vào libC++ 's <array><initializer_list> ở các vị trí thích hợp), tinh chỉnh này thêm static là đủ cho các ví dụ của bạn được chấp nhận.

Sử dụng GCC, các ví dụ vẫn bị từ chối, với chẩn đoán như:

<stdin>:21:61: error: ‘const std::initializer_list<int>{((const int*)(& _ZGRZ4mainE1c0)), 1u}’ is not a constant expression 

Ở đây, _ZGRZ4mainE1c0 là tên đọc sai của mảng đời-mở rộng tạm thời (với thời hạn lưu trữ tĩnh), và chúng ta có thể thấy rằng GCC đang ngầm gọi (constructor) riêng tư (01)(riêng). Tôi không chắc tại sao GCC vẫn từ chối điều này.

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