Khi nào thông tin kiểu chảy ngược trong C ++?


92

Tôi vừa xem Stephan T. Lavavej nói chuyện tại CppCon 2018"Khấu trừ đối số của mẫu lớp học", tại một thời điểm nào đó anh ấy tình cờ nói:

Trong C ++, thông tin kiểu gần như không bao giờ chảy ngược lại ... Tôi phải nói "gần như" vì có một hoặc hai trường hợp, có thể nhiều hơn nhưng rất ít .

Mặc dù cố gắng tìm ra những trường hợp mà anh ấy có thể đang ám chỉ, tôi không thể nghĩ ra bất cứ điều gì. Do đó câu hỏi:

Trong những trường hợp nào tiêu chuẩn C ++ 17 bắt buộc loại thông tin truyền ngược lại?


kết hợp mẫu phù hợp với chuyên môn hóa từng phần và các nhiệm vụ cơ cấu lại.
v.oddou

Câu trả lời:


80

Đây là ít nhất một trường hợp:

struct foo {
  template<class T>
  operator T() const {
    std::cout << sizeof(T) << "\n";
    return {};
  }
};

nếu bạn làm vậy foo f; int x = f; double y = f;, nhập thông tin sẽ chảy ngược "ngược" để tìm ra những gì Ttrong đó operator T.

Bạn có thể sử dụng điều này theo cách nâng cao hơn:

template<class T>
struct tag_t {using type=T;};

template<class F>
struct deduce_return_t {
  F f;
  template<class T>
  operator T()&&{ return std::forward<F>(f)(tag_t<T>{}); }
};
template<class F>
deduce_return_t(F&&)->deduce_return_t<F>;

template<class...Args>
auto construct_from( Args&&... args ) {
  return deduce_return_t{ [&](auto ret){
    using R=typename decltype(ret)::type;
    return R{ std::forward<Args>(args)... };
  }};
}

vì vậy bây giờ tôi có thể làm

std::vector<int> v = construct_from( 1, 2, 3 );

Và nó hoạt động.

Tất nhiên, tại sao không chỉ làm {1,2,3}? Chà, {1,2,3}không phải là một biểu hiện.

std::vector<std::vector<int>> v;
v.emplace_back( construct_from(1,2,3) );

mà phải thừa nhận là yêu cầu nhiều thuật sĩ hơn một chút: Ví dụ trực tiếp . (Tôi phải thực hiện trả về suy ra thực hiện kiểm tra SFINAE của F, sau đó làm cho F trở nên thân thiện với SFINAE tôi phải chặn std :: initializer_list trong toán tử suy ra T.)


Câu trả lời rất thú vị, và tôi đã học được một thủ thuật mới, vì vậy cảm ơn bạn rất nhiều! Tôi đã phải thêm một hướng dẫn khấu trừ mẫu để làm cho ví dụ của bạn được biên dịch , nhưng khác với việc nó hoạt động như một sự quyến rũ!
Massimiliano

5
Vòng &&loại trên operator T()là một liên lạc tuyệt vời; nó giúp tránh tương tác kém bằng autocách gây ra lỗi biên dịch nếu autođược sử dụng sai ở đây.
Justin

1
Điều đó rất ấn tượng, bạn có thể chỉ cho tôi một số tài liệu tham khảo / nói về ý tưởng trong ví dụ không? hoặc có thể nó là bản gốc :) ...
llllllllll

3
@lili Ý tưởng nào? Ta đếm 5: Dùng toán tử T để suy ra kiểu trả về? Sử dụng các thẻ để chuyển kiểu suy luận sang lambda? Sử dụng toán tử chuyển đổi để xây dựng đối tượng vị trí của riêng bạn? Kết nối cả 4?
Yakk - Adam Nevraumont

1
@lili Tha Ví dụ về "cách nâng cao hơn", như tôi đã nói, chỉ có 4 ý tưởng được gắn lại với nhau. Tôi đã dán nhanh cho bài đăng này, nhưng tôi chắc chắn đã thấy nhiều cặp hoặc thậm chí bộ ba của những người được sử dụng cùng nhau. Đó là một loạt các kỹ thuật mù mờ hợp lý (như tootsie phàn nàn), nhưng không có gì mới lạ.
Yakk - Adam Nevraumont

31

Stephan T. Lavavej giải thích trường hợp anh ấy đang nói đến trong một tweet :

Trường hợp tôi đang nghĩ đến là nơi bạn có thể lấy địa chỉ của một hàm được nạp chồng / tạo khuôn mẫu và nếu nó được sử dụng để khởi tạo một biến của một kiểu cụ thể, thì điều đó sẽ phân biệt được biến nào bạn muốn. (Có một danh sách những gì phân biệt.)

chúng ta có thể xem các ví dụ về điều này từ trang cppreference trên Địa chỉ của hàm quá tải , tôi đã loại trừ một số bên dưới:

int f(int) { return 1; } 
int f(double) { return 2; }   

void g( int(&f1)(int), int(*f2)(double) ) {}

int main(){
    g(f, f); // selects int f(int) for the 1st argument
             // and int f(double) for the second

     auto foo = []() -> int (*)(int) {
        return f; // selects int f(int)
    }; 

    auto p = static_cast<int(*)(int)>(f); // selects int f(int)
}

Michael Park cho biết thêm :

Nó cũng không giới hạn trong việc khởi tạo một kiểu cụ thể. Nó cũng có thể suy ra từ số lượng đối số

và cung cấp ví dụ trực tiếp này :

void overload(int, int) {}
void overload(int, int, int) {}

template <typename T1, typename T2,
          typename A1, typename A2>
void f(void (*)(T1, T2), A1&&, A2&&) {}

template <typename T1, typename T2, typename T3,
          typename A1, typename A2, typename A3>
void f(void (*)(T1, T2, T3), A1&&, A2&&, A3&&) {}

int main () {
  f(&overload, 1, 2);
}

mà tôi nói rõ hơn một chút ở đây .


4
Chúng ta cũng có thể mô tả điều này như: các trường hợp mà loại biểu thức phụ thuộc vào ngữ cảnh?
MM

20

Tôi tin vào việc đúc tĩnh các hàm quá tải, dòng chảy sẽ đi theo hướng ngược lại như trong giải quyết quá tải thông thường. Vì vậy, một trong số đó là ngược, tôi đoán.


7
Tôi tin rằng điều này là chính xác. Và đó là khi bạn chuyển một tên hàm cho một kiểu con trỏ hàm; dòng thông tin nhập từ ngữ cảnh của biểu thức (kiểu bạn đang gán cho / construct / etc) ngược lại vào tên của hàm để xác định quá tải nào được chọn.
Yakk - Adam Nevraumont
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.