Я только что наблюдал за выступлением Стефана Т. Лававежа на 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 требует, чтобы информация о типе передавалась в обратном направлении?





Я верю в статическое приведение перегруженных функций, поток идет в противоположном направлении, как при обычном разрешении перегрузки. Думаю, один из них - наоборот.
Я считаю, что это правильно. И это когда вы передаете имя функции типу указателя функции; информация о типе перетекает из контекста выражения (тип, который вы назначаете / конструируете / и т. д.) обратно в имя функции, чтобы определить, какая перегрузка выбрана.
Вот хотя бы один случай:
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.)
Очень интересный ответ, и я узнал новый трюк, так что спасибо вам большое! Мне пришлось добавить руководство по вычету шаблона в скомпилируйте свой пример, но в остальном оно работает как шарм!
Квалификатор && на operator T() - отличный штрих; это помогает избежать плохого взаимодействия с auto, вызывая ошибку компиляции, если здесь auto неправильно используется.
Это очень впечатляет, не могли бы вы указать мне на какую-нибудь ссылку / поговорить об идее в примере? а может оригинал :) ...
@tootsie Нет, это агрегат, и я использовал {}. Мне может понадобиться руководство по вычетам, но я не уверен. Обратите внимание, что вы можете сделать это в 4 раза больше кода и быть вдвое яснее; Я был краток, добавляя сюда новую языковую функцию в виде заголовочного файла #include.
@lili Какая идея? Я считаю 5. Использование оператора T для определения возвращаемых типов? Использование тегов для передачи выведенного типа в лямбду? Используете операторы преобразования для создания собственного объекта размещения? Подключить все 4?
@Yakk В вашем ответе я имел в виду пример "более продвинутого способа". Если это из какого-то разговора / справки / проекта, я бы хотел прочитать больше.
Я понимаю, что вы имеете в виду ... но мой gcc принимает это как агрегат только тогда, когда я передаю тип явно, что-то вроде .. auto l = <lambda>; return deduce_return_t<decltype(l)>{move(l)};
@toot C++ 17 и руководство по дедукции тоже должно это исправить
@lili Tha пример "более продвинутого пути", как я уже сказал, состоит всего из 4 или около того идей, склеенных вместе. Я делал склейку на лету для этого поста, но я наверняка видел много пар или даже троек, которые использовались вместе. Это набор довольно непонятных техник (как жалуется Тутси), но ничего нового.
@Justin, не могли бы вы подробнее рассказать, как квалификатор && помогает вывести ошибку в случае использования auto? Собственно autoкомпилируется и запускается для меня. Я что-то упустил?
@Mike Это непростая ситуация для объяснения, но обратите внимание, что в вашем примере вы неправильно используете этот инструмент. В производственном коде член f будет частным. Квалификатор && означает, что кто-то не может случайно записать auto result = construct_from(1, 2, 3) и ожидать, что result будет содержать значение, тогда как без квалификатора &&, если кто-то использовал result, он мог бы просто скомпилировать, поскольку преобразование могло произойти поздно. Это может быть проблемой, поскольку оценка может происходить несколько раз, если result используется несколько раз, или это может действительно вызвать ошибки, связанные с временем жизни.
@Justin, Хорошее объяснение, спасибо. Определенно умный вариант использования квалификатора &&.
Стефан Т. Лававей объяснил случай, о котором он говорил, в твите:
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);
}
который я немного уточняю подробнее здесь.
Мы могли бы также описать это как: случаи, когда тип выражения зависит от контекста?
частичная специализация сопоставления с образцом и деструктурирующие присваивания.