Làm thế nào để giải quyết vấn đề về đọc dữ liệu của biến không phải là constexpr 'a' không được phép trong một biểu thức không đổi, với boost.hana


8

Tôi đang sử dụng c ++ 17 với Boost.hana để viết một số chương trình lập trình meta. Một vấn đề khiến tôi băn khoăn là loại biểu thức nào có thể được sử dụng trong ngữ cảnh constexpr như static_assert. Đây là một ví dụ:

#include <boost/hana.hpp>

using namespace boost::hana::literals;

template <typename T>
class X {
public:
    T data;

    constexpr explicit X(T x) : data(x) {}

    constexpr T getData() {
        return data;
    }
};


int main() {
    {   // test1
        auto x1 = X(1_c);
        static_assert(x1.data == 1_c);
        static_assert(x1.getData() == 1_c);
    }
    {   //test2.1
        auto x2 = X(boost::hana::make_tuple(1_c, 2_c));
        static_assert(x2.data[0_c] == 1_c);

        // static_assert(x2.getData()[0_c] == 1_c); // read of non-constexpr variable 'x2' is not allowed in a constant expression
    }
    {   //test2.2
        auto x2 = X(boost::hana::make_tuple(1_c, 2_c));
        auto data = x2.getData();
        static_assert(data[0_c] == 1_c);
    }
}

Đầu tiên tôi viết một lớp X với dữ liệu trường và một hàm truy cập getData () . Trong chính () phần test1 của , x1.datax1.getData () hoạt động giống như tôi mong đợi. Nhưng trong phần test2 , việc thay đổi đối số thành tuple boost :: hana, static_assert(x2.data[0_c] == 1_c)vẫn hoạt động tốt nhưng static_assert(x2.getData()[0_c] == 1_c)không biên dịch được, với lỗi ' đọc biến không phải là constexpr' x2 'không được phép trong biểu thức không đổi '. Những gì chúng tôi đã làm là nếu tôi tách x2.getData()[0_c]ra auto data = x2.getData();static_assert(data[0_c] == 1_c);nó biên dịch lại tốt. Tôi mong họ cư xử như vậy. Vì vậy, bất cứ ai có thể giúp giải thích tại sao x2.getData()[0_c]không thể được sử dụng trong static_assert trong ví dụ này?

Để sao chép: clang ++ 8.0 -I / path / to / hana-1.5.0 / bao gồm -std = c ++ 17 Test.cpp


Thật thú vị, GCC biên dịch nó thành công . Tôi đã giảm mã xuống cái này ngắn hơn .
xskxzr

constexprlà mất tích trên x2data, consttrên getData. godbolt.org/z/ZNL2BK
Maxim Egorushkin

@xskxzr Thật không may, tôi chỉ có Clang để sử dụng cho mục tiêu của mình.
dài

Câu trả lời:


5

Vấn đề là boost::hana::tuplekhông có một constructor sao chép.

Nó có một constructorvẻ bề ngoài giống như một constructor sao chép:

template <typename ...dummy, typename = typename std::enable_if<
    detail::fast_and<BOOST_HANA_TT_IS_CONSTRUCTIBLE(Xn, Xn const&, dummy...)...>::value
>::type>
constexpr tuple(tuple const& other)
    : tuple(detail::from_index_sequence_t{},
            std::make_index_sequence<sizeof...(Xn)>{},
            other.storage_)
{ }

Nhưng vì đây là một mẫu, nó không phải là một hàm tạo sao chép .

boost::hana::tuplekhông có hàm tạo sao chép, nên một hàm được khai báo ngầm định và được định nghĩa là mặc định (nó không bị loại bỏ vì boost::hana::tuplekhông có bất kỳ bản sao hoặc di chuyển hàm tạo hoặc toán tử gán nào, bởi vì, bạn đoán nó, chúng không thể là mẫu).

Ở đây chúng ta thấy phân kỳ thực hiện , được thể hiện trong hành vi của chương trình sau:

struct A {
    struct B {} b;
    constexpr A() {};
    // constexpr A(A const& a) : b{a.b} {}    // #1
};
int main() {
    auto a = A{};
    constexpr int i = (A{a}, 0);
}

gcc chấp nhận, trong khi Clang và MSVC từ chối, nhưng chấp nhận nếu dòng không #1bị lỗi. Đó là, các trình biên dịch không đồng ý về việc liệu hàm tạo sao chép được định nghĩa ngầm định của một lớp trống không (trực tiếp) có được phép sử dụng trong bối cảnh đánh giá không đổi.

Theo định nghĩa của hàm tạo sao chép được xác định ngầm định , không có cách nào mà # 1 khác với constexpr A(A const&) = default;gcc là chính xác. Cũng lưu ý rằng nếu chúng tôi cung cấp cho B một hàm tạo sao chép constexpr do người dùng định nghĩa Clang và MSVC chấp nhận lại, thì vấn đề có vẻ là các trình biên dịch này không thể theo dõi khả năng xây dựng bản sao constexpr của các lớp có thể sao chép ngầm. Đã sửa lỗi cho MSVCClang (đã sửa cho Clang 11).

Lưu ý rằng việc sử dụng operator[]là cá trích đỏ; vấn đề là liệu trình biên dịch có cho phép gọi getData()(cấu trúc sao chép T) trong bối cảnh đánh giá không đổi như không static_assert.

Rõ ràng, giải pháp lý tưởng sẽ là Boost.Hana sửa lỗi boost::hana::tuplesao cho nó có các hàm tạo sao chép / di chuyển thực tế và các toán tử gán / sao chép di chuyển. (Điều này sẽ khắc phục trường hợp sử dụng của bạn vì mã sẽ gọi các hàm tạo sao chép do người dùng cung cấp, được phép trong bối cảnh đánh giá không đổi.) Như một cách giải quyết , bạn có thể xem xét hack getData()để phát hiện trường hợp không có trạng thái T:

constexpr T getData() {
    if (data == T{})
        return T{};
    else
        return data;
}

Tôi không hiểu tại sao trình xây dựng sao chép mặc định không hoạt động ... Bạn có thể thử xem lại nó cho tôi không?
Antoine Morrier

@AntoineMorrier Tôi tin rằng đây là một lỗi trong tiếng kêu; có vẻ như khó nhận ra rằng các lớp rỗng đệ quy có thể được sao chép trong ngữ cảnh constexpr.
ecatmur

Cảm ơn. Câu trả lời rất hữu ích! Vì vậy, bạn có thể giải thích thêm tại sao test2.2phần được chấp nhận bởi Clang? (Tôi đã chỉnh sửa câu hỏi ban đầu và tách test2 thành test2.1 và test2.2) Tôi mong họ sẽ hành xử giống nhau.
Dài

@Long bit mà Clang không hài lòng là việc xây dựng bản sao hana::tuplexảy ra trong quá trình hoàn trả getData. Trong test2.2, bản sao xảy ra bên ngoài bối cảnh đánh giá không đổi, vì vậy Clang vẫn ổn với nó.
ecatmur

er .. nó hơi khó, ít nhất là đối với tôi để hiểu nó .. getData()không được phép ở đây nhưng hãy ra ngoài và giới thiệu một tính khí sau đó được chấp nhận ..
Long

1

Vấn đề là bởi vì bạn đang cố gắng truy xuất giá trị thời gian chạy và kiểm tra nó khi biên dịch.

Những gì bạn có thể làm là buộc biểu thức tại thời gian biên dịch qua a decltypevà nó sẽ hoạt động như một bùa mê :).

static_assert(decltype(x2.getData()[0_c]){} == 1_c);

#include <boost/hana.hpp>

using namespace boost::hana::literals;

template <typename T>
class X {
public:
    T data;

   constexpr explicit X(T x) : data(x) {}

   constexpr T getData() {
        return data;
    }
};


int main() {
    {   // test1
        auto x1 = X(1_c);
        static_assert(x1.data == 1_c);
        static_assert(x1.getData() == 1_c);
    }
    {   //test2
        auto x2 = X(boost::hana::make_tuple(1_c, 2_c));
        static_assert(x2.data[0_c] == 1_c);

         static_assert(decltype(x2.getData()[0_c]){} == 1_c);

        auto data = x2.getData();
        static_assert(data[0_c] == 1_c);
    }
}

Bây giờ biểu thức được ước tính tại thời gian biên dịch, vì vậy loại được biết đến tại thời gian biên dịch và vì nó cũng có thể xây dựng được tại thời điểm tính toán, nên có thể sử dụng nó trong static_assert


Cảm ơn câu trả lời. Tôi có thể hiểu việc sử dụng static_assert(decltype(x2.getData()[0_c]){} == 1_c)có thể hoạt động tốt, nhưng tôi vẫn muốn tiết kiệm decltypevì điều đó sẽ tiết kiệm rất nhiều. Tôi đoán bạn đang nói x2.getData()đang truy xuất một giá trị thời gian chạy để nó không xuất hiện trong biểu thức static_assert. Sau đó, tôi không hiểu tại sao phần x1.getData()trong test1 và dữ liệu trong data[0_c]phần test2 có thể hoạt động tốt. Sự khác biệt của họ là gì?
Dài

Thực sự không có bất kỳ giá trị thời gian chạy nào được truy cập trong mọi trường hợp, nhưng có lẽ tiêu chuẩn không cho phép trình biên dịch kiểm tra điều đó.
Jason Rice

0

Vì vậy, trước hết, bạn đang thiếu vòng loại const trong getData()phương thức, vì vậy nó phải là:

constexpr T getData() const

Không có biến nào được quảng bá, ít nhất là từ quan điểm tiêu chuẩn, là constexpr nếu nó không được đánh dấu là constexpr.

Lưu ý rằng điều này là không cần thiết đối với x1loại Xchuyên dụng với hana :: integ_constant, vì kết quả 1_clà loại không có hàm tạo sao chép do người dùng xác định, không chứa bất kỳ dữ liệu nào trong nội bộ, do đó, hoạt động sao chép getData()trong thực tế là không hoạt động , vì vậy biểu thức: static_assert(x1.getData() == 1_c); là tốt, vì không có bản sao thực sự được thực hiện (cũng không cần truy cập vào thiscon trỏ không phải là const x1).

Điều này rất khác nhau đối với vùng chứa của bạn có hana::tuplechứa cấu trúc sao chép thực tế hana::tupletừ dữ liệu trong x2.datatrường. Điều này đòi hỏi quyền truy cập thực tế vào thiscon trỏ của bạn - điều không cần thiết trong trường hợp x1, đó cũng không phải là biến constexpr.

Điều này có nghĩa là bạn đang thể hiện ý định của mình sai với cả hai x1x2, ít nhất là cần thiết x2, để đánh dấu các biến này là constexpr. Cũng lưu ý rằng, sử dụng bộ dữ liệu trống, là một chuyên môn chung về cơ bản trống (không có người xây dựng bản sao do người dùng xác định) hana::tuple, hoạt động trơn tru (phần test3):

#include <boost/hana.hpp>

using namespace boost::hana::literals;

template <typename T>
class X {
public:
    T data;

    constexpr explicit X(T x) : data(x) {}

    constexpr T getData() const {
        return data;
    }
};

template<typename V>
constexpr auto make_X(V value)
{
    return value;
}

int main() {
    {   // test1
        auto x1 = X(1_c);
        static_assert(x1.data == 1_c);
        static_assert(x1.getData() == 1_c);
    }
    {   //test2
        constexpr auto x2 = X(boost::hana::make_tuple(1_c, 2_c));
        static_assert(x2.data[0_c] == 1_c);

        static_assert(x2.getData()[0_c] == 1_c); // read of non-constexpr variable 'x2' is not allowed in a constant expression

        auto data = x2.getData();
        static_assert(data[0_c] == 1_c);
    }
    {   //test3
        auto x3 = X(boost::hana::make_tuple());
        static_assert(x3.data == boost::hana::make_tuple());

        static_assert(x3.getData() == boost::hana::make_tuple());
    }
}

Tôi chưa đọc hết câu trả lời, nhưng một phương thức constexpr có thể không phải là const bình thường.
Antoine Morrier

Tôi không đồng ý rằng đó x1là một loại trống. Bất kỳ trường hợp nào Xcó một thành viên dữ liệu. Cũng hana::tuplechứa các kiểu trống là chính nó trống vì nó sử dụng tối ưu hóa cơ sở trống. Bạn có thể đang nhắm mục tiêu đổ lỗi cho trình xây dựng sao chép vì Clang hoặc libc ++ có thể đang làm điều gì đó khó khăn với std::integral_constant.
Jason Rice

Và, có cách nào để tôi không phải thêm constexpr cho khai báo x2 không? Tôi muốn X có thể được khởi tạo với cả giá trị không đổi và giá trị thời gian chạy. Chẳng hạn như: `` `int g = 1; int main () {{/ * test3 * / auto x3 = X (g); }} `` `Tôi hy vọng nó cũng có thể hoạt động hoàn hảo. Nhưng việc thêm một constexpr vào x3 sẽ không được biên dịch, có lỗi:constexpr variable 'x3' must be initialized by a constant expression
Dài

@AntoineMorrier: Có, nhưng điều này vẫn ổn miễn là bạn không sử dụng const thiscon trỏ và bạn không may sử dụng nó x2trong trường hợp static_assert. (trong trường hợp của x1 - đó là một cuộc thảo luận thêm :)).
Michał Łoś

@JasonRice: Vâng, đó là sự thật, tôi sẽ điều chỉnh câu trả lời, vì tôi không chính xác: cả hai tất nhiên không có bất kỳ trường không tĩnh nào. Tuy nhiên, và đây là những gì câu trả lời của tôi thiếu, xin lưu ý rằng trong khihana::integral_constant có hàm tạo, trình xây dựng do trình biên dịch xác định, hana::tuplecó người dùng định nghĩa. Ngoài ra, vì có một chuyên môn cho tuple trống, không có bất kỳ hàm tạo nào, cùng một mã cho các tuple trống hoạt động: godbolt.org/z/ZeEVQN
Michał oś
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.