Когда в C++ информация о типах передается в обратном направлении?

Я только что наблюдал за выступлением Стефана Т. Лававежа на CppCon 2018 на тему «Вывод аргументов из шаблона класса», где в какой-то момент он, между прочим, говорит:

In C++ type information almost never flows backwards ... I had to say "almost" because there's one or two cases, possibly more but very few.

Несмотря на попытки выяснить, о каких случаях он может иметь в виду, я ничего не мог придумать. Отсюда вопрос:

В каких случаях стандарт C++ 17 требует, чтобы информация о типе передавалась в обратном направлении?

частичная специализация сопоставления с образцом и деструктурирующие присваивания.

v.oddou 13.11.2018 06:11
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
92
1
5 210
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Я верю в статическое приведение перегруженных функций, поток идет в противоположном направлении, как при обычном разрешении перегрузки. Думаю, один из них - наоборот.

Я считаю, что это правильно. И это когда вы передаете имя функции типу указателя функции; информация о типе перетекает из контекста выражения (тип, который вы назначаете / конструируете / и т. д.) обратно в имя функции, чтобы определить, какая перегрузка выбрана.

Yakk - Adam Nevraumont 12.11.2018 23:08
Ответ принят как подходящий

Вот хотя бы один случай:

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

если вы сделаете foo f; int x = f; double y = f;, информация о типе будет течь «в обратном направлении», чтобы выяснить, что такое T в operator T.

Вы можете использовать это более продвинутым способом:

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)... };
  }};
}

так что теперь я могу сделать

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

и это работает.

Конечно, а почему бы просто не сделать {1,2,3}? Что ж, {1,2,3} - это не выражение.

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

что, по общему признанию, требует немного больше волшебства: Живой пример. (Мне нужно сделать вывод вывода, чтобы выполнить проверку SFINAE для F, затем сделать F дружественным к SFINAE, и, мне нужно заблокировать std :: initializer_list в операторе deduce_return_t T.)

Очень интересный ответ, и я узнал новый трюк, так что спасибо вам большое! Мне пришлось добавить руководство по вычету шаблона в скомпилируйте свой пример, но в остальном оно работает как шарм!

Massimiliano 12.11.2018 23:28

Квалификатор && на operator T() - отличный штрих; это помогает избежать плохого взаимодействия с auto, вызывая ошибку компиляции, если здесь auto неправильно используется.

Justin 12.11.2018 23:42

Это очень впечатляет, не могли бы вы указать мне на какую-нибудь ссылку / поговорить об идее в примере? а может оригинал :) ...

llllllllll 13.11.2018 00:59

@tootsie Нет, это агрегат, и я использовал {}. Мне может понадобиться руководство по вычетам, но я не уверен. Обратите внимание, что вы можете сделать это в 4 раза больше кода и быть вдвое яснее; Я был краток, добавляя сюда новую языковую функцию в виде заголовочного файла #include.

Yakk - Adam Nevraumont 13.11.2018 01:10

@lili Какая идея? Я считаю 5. Использование оператора T для определения возвращаемых типов? Использование тегов для передачи выведенного типа в лямбду? Используете операторы преобразования для создания собственного объекта размещения? Подключить все 4?

Yakk - Adam Nevraumont 13.11.2018 01:13

@Yakk В вашем ответе я имел в виду пример "более продвинутого способа". Если это из какого-то разговора / справки / проекта, я бы хотел прочитать больше.

llllllllll 13.11.2018 01:22

Я понимаю, что вы имеете в виду ... но мой gcc принимает это как агрегат только тогда, когда я передаю тип явно, что-то вроде .. auto l = <lambda>; return deduce_return_t<decltype(l)>{move(l)};

Tootsie 13.11.2018 01:30

@toot C++ 17 и руководство по дедукции тоже должно это исправить

Yakk - Adam Nevraumont 13.11.2018 03:04

@lili Tha пример "более продвинутого пути", как я уже сказал, состоит всего из 4 или около того идей, склеенных вместе. Я делал склейку на лету для этого поста, но я наверняка видел много пар или даже троек, которые использовались вместе. Это набор довольно непонятных техник (как жалуется Тутси), но ничего нового.

Yakk - Adam Nevraumont 13.11.2018 03:26

@Justin, не могли бы вы подробнее рассказать, как квалификатор && помогает вывести ошибку в случае использования auto? Собственно autoкомпилируется и запускается для меня. Я что-то упустил?

Mike 17.11.2018 12:09

@Mike Это непростая ситуация для объяснения, но обратите внимание, что в вашем примере вы неправильно используете этот инструмент. В производственном коде член f будет частным. Квалификатор && означает, что кто-то не может случайно записать auto result = construct_from(1, 2, 3) и ожидать, что result будет содержать значение, тогда как без квалификатора &&, если кто-то использовал result, он мог бы просто скомпилировать, поскольку преобразование могло произойти поздно. Это может быть проблемой, поскольку оценка может происходить несколько раз, если result используется несколько раз, или это может действительно вызвать ошибки, связанные с временем жизни.

Justin 19.11.2018 20:12

@Justin, Хорошее объяснение, спасибо. Определенно умный вариант использования квалификатора &&.

Mike 19.11.2018 20:23

Стефан Т. Лававей объяснил случай, о котором он говорил, в твите:

The case I was thinking of is where you can take the address of an overloaded/templated function and if it’s being used to initialize a variable of a specific type, that will disambiguate which one you want. (There’s a list of what disambiguates.)

мы можем видеть примеры этого из страница cppreference на адресе перегруженной функции, я исключил несколько ниже:

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)
}

Майкл Парк добавляет:

It's not limited to initializing a concrete type, either. It could also infer just from the number of arguments

и предоставляет этот живой пример:

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.M 13.11.2018 00:56

Другие вопросы по теме