Tại sao tôi không thể truy xuất chỉ mục của một biến thể và sử dụng nó để lấy nội dung của nó?


10

Tôi đang cố gắng truy cập nội dung của một biến thể. Tôi không biết những gì trong đó, nhưng may mắn thay, biến thể này. Vì vậy, tôi nghĩ rằng tôi sẽ chỉ hỏi biến thể nó là chỉ mục nào và sau đó sử dụng chỉ mục đó cho std::getnội dung của nó.

Nhưng điều này không biên dịch:

#include <variant>

int main()
{
  std::variant<int, float, char> var { 42.0F };

  const std::size_t idx = var.index();

  auto res = std::get<idx>(var);

  return 0;
}

Lỗi xảy ra trong std::getcuộc gọi:

error: no matching function for call to get<idx>(std::variant<int, float, char>&)’
   auto res = std::get<idx>(var);
                               ^
In file included from /usr/include/c++/8/variant:37,
                 from main.cpp:1:
/usr/include/c++/8/utility:216:5: note: candidate: template<long unsigned int _Int, class _Tp1, class _Tp2> constexpr typename std::tuple_element<_Int, std::pair<_Tp1, _Tp2> >::type& std::get(std::pair<_Tp1, _Tp2>&)’
     get(std::pair<_Tp1, _Tp2>& __in) noexcept
     ^~~
/usr/include/c++/8/utility:216:5: note:   template argument deduction/substitution failed:
main.cpp:9:31: error: the value of idx is not usable in a constant expression
   auto res = std::get<idx>(var);
                               ^
main.cpp:7:15: note: std::size_t idx is not const
   std::size_t idx = var.index();
               ^~~

Làm thế nào tôi có thể sửa lỗi này?


3
Tôi nghi ngờ lỗi bạn nhận được có liên quan đến chỉ số không phải là biểu thức không đổi. Vui lòng gửi thông báo lỗi trình biên dịch để chúng tôi có thể cung cấp trợ giúp có ý nghĩa.
patatahooligan

Thiếu constexpr?
Rlyeh

Rất tiếc! Bạn đã nói về một lỗi, nhưng bạn đã không đăng văn bản chính xác của lỗi.
Jonathan Wood

1
Xin lỗi vì thiếu sót, tôi đã cập nhật câu hỏi
Alex

Câu trả lời:


4

Về cơ bản, bạn không thể.

Bạn đã viết:

Tôi không biết những gì trong đó, nhưng may mắn thay, biến thể không

... nhưng chỉ vào thời gian chạy, không phải lúc biên dịch.
Và điều đó có nghĩa là idxgiá trị của bạn không phải là thời gian biên dịch.
điều đó có nghĩa là bạn không thể sử dụng get<idx>()trực tiếp.

Một cái gì đó bạn có thể làm là có một tuyên bố chuyển đổi; xấu, nhưng nó sẽ làm việc:

switch(idx) {
case 0: { /* code which knows at compile time that idx is 0 */ } break;
case 1: { /* code which knows at compile time that idx is 1 */ } break;
// etc. etc.
}

Điều này là khá xấu xí tuy nhiên. Như các ý kiến ​​đề xuất, bạn cũng có thể std::visit()(không khác lắm so với mã ở trên, ngoại trừ việc sử dụng các đối số khuôn mẫu thay vì rõ ràng này) và tránh chuyển đổi hoàn toàn. Đối với các cách tiếp cận dựa trên chỉ mục khác (không cụ thể std::variant), xem:

Thành ngữ để mô phỏng các tham số mẫu số thời gian chạy?


@Caleth: Vâng. Đã chỉnh sửa.
einpoklum

5

Trình biên dịch cần biết giá trị của idxthời gian biên dịch std::get<idx>()để làm việc, bởi vì nó đang được sử dụng làm đối số mẫu.

Tùy chọn đầu tiên: Nếu mã có nghĩa là chạy trong thời gian biên dịch, thì hãy thực hiện mọi thứ constexpr:

constexpr std::variant<int, float, char> var { 42.0f };

constexpr std::size_t idx = var.index();

constexpr auto res = std::get<idx>(var);

Này hoạt động vì std::variantconstexprthân thiện (nhà thầu và phương thức của nó là tất cả constexpr).

Tùy chọn thứ hai: Nếu mã không có nghĩa là chạy trong thời gian biên dịch, rất có thể là trường hợp đó, trình biên dịch không thể suy ra tại thời điểm biên dịch loại res, bởi vì nó có thể là ba thứ khác nhau (int , floathoặc char). C ++ là một ngôn ngữ được gõ tĩnh và trình biên dịch phải có thể suy ra loại auto res = ...từ biểu thức theo sau (nghĩa là nó phải luôn cùng loại).

Bạn có thể dùng std::get<T> , với loại thay vì chỉ mục, nếu bạn đã biết nó sẽ là gì:

std::variant<int, float, char> var { 42.0f }; // chooses float

auto res = std::get<float>(var);

Nói chung, sử dụng std::holds_alternative để kiểm tra xem biến thể có giữ từng loại đã cho hay không và xử lý chúng một cách riêng biệt:

std::variant<int, float, char> var { 42.0f };

if (std::holds_alternative<int>(var)) {
    auto int_res = std::get<int>(var); // int&
    // ...
} else if (std::holds_alternative<float>(var)) {
    auto float_res = std::get<float>(var); // float&
    // ...
} else {
    auto char_res = std::get<char>(var); // char&
    // ...
}

Ngoài ra, bạn có thể sử dụng std::visit . Điều này phức tạp hơn một chút: bạn có thể sử dụng hàm lambda / templated không theo kiểu và không hoạt động cho tất cả các kiểu của biến thể hoặc vượt qua hàm functor với toán tử cuộc gọi quá tải:

std::variant<int, float, char> var { 42.0f };

std::size_t idx = var.index();

std::visit([](auto&& val) {
    // use val, which may be int&, float& or char&
}, var);

Xem std :: ghé thăm để biết chi tiết và ví dụ.


3

Vấn đề là ở đó std::get<idx>(var); yêu cầu (choidx ) một giá trị thời gian biên dịch đã biết.

Vì vậy, một constexprgiá trị

// VVVVVVVVV
   constexpr std::size_t idx = var.index();

Nhưng để khởi tạo idxnhư constexpr, cũng varphảiconstexpr

// VVVVVVVVV
   constexpr std::variant<int, float, char> var { 42.0F };

Đây là một biến thể constexpr không phải là biến thể.
Davis Herring

@DavisHerring - Điều đó cũng đúng.
max66

2

Vấn đề phát sinh từ các mẫu được khởi tạo tại thời gian biên dịch trong khi chỉ mục bạn đang nhận được tính vào thời gian chạy. Tương tự, các loại C ++ cũng được định nghĩa tại thời gian biên dịch nên ngay cả với autokhai báo, resphải có một loại cụ thể để chương trình được định dạng tốt. Điều này có nghĩa là ngay cả khi không có hạn chế trên mẫu, những gì bạn đang cố gắng thực hiện là không thể đối với biểu thức không cố định std::variants. Làm thế nào một người sẽ làm việc xung quanh này?

Thứ nhất, nếu biến thể của bạn thực sự là một biểu thức không đổi, mã sẽ biên dịch và hoạt động như mong đợi

#include <variant>

int main()
{
  constexpr std::variant<int, float, char> var { 42.0f };

  constexpr std::size_t idx = var.index();

  auto res = std::get<idx>(var);

  return 0;
}

Nếu không, bạn sẽ phải sử dụng một số cơ chế phân nhánh thủ công

if (idx == 0) {
    // Now 'auto' will have a concrete type which I've explicitly used
    int value == std::get<0>(var);
}

Bạn có thể xác định các nhánh này bằng cách sử dụng mẫu khách truy cập, xem std :: visit .


1

Điều này vốn dĩ không thể có trong mô hình của C ++; xem xét

template<class T> void f(T);
void g(std::variant<int,double> v) {
  auto x=std::get<v.index()>(v);
  f(x);
}

Cái nào fđang được gọi, f<int>hay f<double>? Nếu đó là cả hai, thì điều đó có nghĩa là gcó một nhánh (mà nó không có) hoặc có hai phiên bản g(chỉ đẩy vấn đề lên người gọi của nó). Và hãy nghĩ về các f(T,U,V,W)nơi mà trình biên dịch dừng lại?

Thực sự có một đề xuất về JIT cho C ++ sẽ cho phép những thứ như thế này bằng cách biên dịch các phiên bản bổ sung fkhi chúng được gọi, nhưng còn rất sớm.

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.