2015-01-13 12 views
8

Khi mẫu biểu thức được triển khai bằng CRTP, lớp ở đầu hệ thống phân cấp biểu thức sử dụng downcasting từ gốc đến gốc để thực hiện một số thao tác của nó. Theo Clang-3,5 (-std=c++1y), chăn màn này nên là bất hợp pháp ở constexpr chức năng:constexpr và CRTP: không đồng bộ trình biên dịch

test.cpp:42:16: error: static_assert expression is not an integral constant expression 
     static_assert(e() == 0, ""); 
         ^~~~~~~~ 
test.cpp:11:26: note: cannot cast object of dynamic type 'const base<derived>' to type 'const derived' 
     const noexcept { return static_cast<const Derived&>(*this)(); } 

GCC hạnh phúc compiles the code. Vì vậy, ai đúng? Nếu Clang là đúng, mà C++ 14 hạn chế trên constexpr chức năng làm cho downcasting bất hợp pháp?

Đây là MWe:

template <class Derived> 
class base 
{ 
public: 
    constexpr auto operator()() 
    const noexcept { return static_cast<const Derived&>(*this)(); } 
}; 

class derived : public base<derived> 
{ 
public: 
    constexpr auto operator()() 
    const noexcept { return 0; } 
}; 

template <class A, class B> 
class expr : public base<expr<A, B>> 
{ 
    const A m_a; 
    const B m_b; 
public: 
    constexpr explicit expr(const A a, const B b) 
    noexcept : m_a(a), m_b(b) {} 

    constexpr auto operator()() 
    const noexcept { return m_a() + m_b(); } 
}; 

template <class D1, class D2> 
constexpr auto foo(const base<D1>& d1, const base<D2>& d2) 
noexcept { return expr<base<D1>, base<D2>>{d1, d2}; } 

int main() 
{ 
    constexpr auto d = derived{}; 
    constexpr auto e = foo(d, d); 
    static_assert(e() == 0, ""); 
} 

Trả lời

9

Đối với operator() trong base để làm một hợp lệ static_cast, đối tượng hầu hết có nguồn gốc từ this điểm đến phải là kiểu Derived (hoặc một lớp con của chúng). Tuy nhiên, các thành viên của e thuộc loại base<derived>, không phải là số derived. Trong dòng

const noexcept { return m_a() + m_b(); } 

m_a là loại base<derived>, và base<derived>::operator() được gọi là - với một đối tượng nhất có nguồn gốc từ các loại base<derived>.
Do đó, dàn diễn viên cố gắng truyền *this thành tham chiếu đến loại đối tượng mà thực tế nó không đề cập đến; hoạt động mà có thể có hành vi undefined như [expr.static.cast]/2 mô tả:

Một vế trái của loại “CV1B,” trong đó B là một loại lớp, có thể được đúc để loại “tài liệu tham khảo đến cv2D ", trong đó D là loại có nguồn gốc (khoản 10) từ B [..]. Nếu đối tượng thuộc loại “cv1 B” thực sự là một đối tượng con của một đối tượng thuộc loại D, kết quả đề cập đến cho đối tượng kèm theo loại D. Nếu không, hành vi là không xác định.

Và sau đó, [expr.const]/2 áp dụng:

Một có điều kiện thể hiệne là một biểu thức cốt lõi liên tục trừ khi việc thẩm định e, theo các quy tắc của trừu tượng máy (1.9), sẽ đánh giá một trong các biểu thức sau:

(2.5) - một hoạt động mà có thể có hành vi undefined

Thay vào đó, viết lại foo như sau:

template <class D1, class D2> 
constexpr auto foo(const D1& d1, const D2& d2) 
noexcept { return expr<D1, D2>{d1, d2}; } 

Và mã works fine.

5

Dường như với tôi rằng Clang là đúng trong trường hợp này. Loại econst expr<base<derived>, base<derived>>, vì vậy m_am_b có loại base<derived>, thay vì derived. Nói cách khác, bạn có slicedd khi sao chép nó vào m_am_b.

+0

Tôi nghĩ rằng hỏi đang tìm kiếm tài liệu tham khảo tiêu chuẩn. – Columbo

+0

@Columbo Tôi sẽ cố gắng tìm một số. Tuy nhiên, như một quy luật chung nhất (tất cả?) Những thứ mà hành vi không xác định tại thời gian chạy là các lỗi trong các đánh giá 'constexpr'. –

+0

Tôi nghĩ mã là tốt, hãy để tôi có một cái nhìn gần hơn – Columbo

2

Đây là một Rework phong nha của mã ban đầu của bạn. UB được lấy ra, và nó kéo dài độc đáo:

namespace library{ 
    template <class Derived> 
    class base 
    { 
    public: 
    constexpr Derived const& self() const noexcept { return static_cast<const Derived&>(*this); } 

    constexpr auto operator()() 
    const noexcept { return self()(); } 
    }; 

    template <class A, class B> 
    class expr : public base<expr<A, B>> 
    { 
    const A m_a; 
    const B m_b; 
    public: 
    constexpr explicit expr(const A a, const B b) 
    noexcept : m_a(a), m_b(b) {} 

    constexpr auto operator()() 
    const noexcept { return m_a() + m_b(); } 
    }; 

    template <class D1, class D2> 
    constexpr auto foo(const base<D1>& d1, const base<D2>& d2) 
    noexcept { return expr<D1, D2>{d1.self(), d2.self()}; } 
} 

namespace client { 
    class derived : public library::base<derived> { 
    public: 
    constexpr auto operator()() 
    const noexcept { return 0; } 
    }; 
} 


int main() 
{ 
    constexpr auto d = client::derived{}; 
    constexpr auto e = foo(d, d); 
    static_assert(e() == 0, ""); 
} 

Về cơ bản mỗi base<X>phải là một X. Vì vậy, khi bạn lưu nó, bạn lưu nó như một X, không phải là một base<X>. Chúng ta có thể truy cập vào X qua base<X>::self() trong một thời trang constexpr.

Bằng cách làm nó theo cách này, chúng ta có thể đưa máy móc vào namespace library. foo có thể được tìm thấy qua ADL, và nếu bạn (ví dụ) bắt đầu thêm các nhà khai thác của bạn biểu hiện mẫu như mã, bạn sẽ không phải tự nhập họ cho main bạn để làm việc.

bạn derived là một lớp được tạo ra bởi mã khách hàng cho thư viện của bạn, vì vậy đi trong không gian tên khác. Nó ghi đè () như nó vui lòng, và nó "chỉ hoạt động".

Ví dụ ít giả tạo hơn sẽ thay thế foo bằng operator+ và lợi thế của kiểu này trở nên rõ ràng. main trở thành constexpr auto e = d+d; mà không cần phải using library::operator+.

Những thay đổi đã thực hiện được thêm một phương pháp self()-base để truy cập Derived, sử dụng nó để loại bỏ một static_cast s trong (), và có foo trả về một expr<D1, D2> thay vì một expr<base<D1>, base<D2>>.

+0

Tại sao là 'self' cần thiết? – Columbo

+0

@columbo giúp nhập cùng một lần một lần nữa? Chúng tôi muốn có nguồn gốc từ cơ sở CRTP khá thường xuyên. – Yakk

+0

@columbo hoặc bạn hỏi tại sao không chỉ làm cho 'foo' mất' D1' và 'D2' trực tiếp? Dễ dàng hơn so với thử nghiệm sfinae rằng 'cơ sở ' là một cơ sở của 'D1' là chỉ cần mất 'cơ sở ' và gọi' tự() 'để lấy lại cho' D1'. Và chúng tôi nhận được ADL. – Yakk

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