#include <iostream>
#include <string>
#include <functional>
int main()
{
using std::placeholders::_1;
auto foo = [](auto a, auto b) { std::cout << "{ "<< a << ", " << b << " }" << std::endl; };
auto f = std::bind(foo, _1, _1);
f(std::string("Hello")); // -> { , Hello }
}
Почему так сделано — это понятно. Но ошибку искал часа полтора. Так что, будьте осмотрительны, остерегайтесь выходить на болота в ночное время ну, в общем, вы поняли.
--
Не можешь достичь желаемого — пожелай достигнутого.
Здравствуйте, rg45, Вы писали:
R>Почему так сделано — это понятно. Но ошибку искал часа полтора. Так что, будьте осмотрительны, остерегайтесь выходить на болота в ночное время ну, в общем, вы поняли.
Вот поэтому всегда лучше вместо std::bind писать лямбды и руками в них делать, что хочешь.
Это и нагляднее, и меньше внезапных спецэффектов.
auto f = std::bind(foo, _1, _1);
auto g = [foo](auto a) { foo(a, a); };
Бинд ещё и с арностью довольно свободно обращается.
f(std::string("Hello"), 100, 200); // сколько-угодно-арная функция же! просто возьмём только _1
g(std::string("Hello"), 100, 200); // ошибка компиляции, всё честно
А уж какие нечеловекочитаемые ошибки компиляции выводит бинд, по сравнению с лямбдой...
Здравствуйте, Кодт, Вы писали:
К>Вот поэтому всегда лучше вместо std::bind писать лямбды и руками в них делать, что хочешь. К>Это и нагляднее, и меньше внезапных спецэффектов.
Да, в принципе, согласен. Но есть нюанс, как обычно. Плюс std::bind в том, что он генерует вызываемую сущность, уже обложенную констрейнтами, которые проверяют совместимость формальных и фактических параметров. И если фактические аргументы несовместимы с вызваемой сущностью, компилятор укажет на точку вызова в клиентском коде. Лямбды же, как и обычные функции, нужно обкладывать констрейнтами собственноручно, а это не всегда простая задача. А если констрейнты дырявые (или вообще отсутсвуют), то ошибку придется ловить в потрохах кода и потом самопехом добираться до точки вызова.
--
Не можешь достичь желаемого — пожелай достигнутого.
Здравствуйте, rg45, Вы писали:
R>Да, в принципе, согласен. Но есть нюанс, как обычно. Плюс std::bind в том, что он генерует вызываемую сущность, уже обложенную констрейнтами, которые проверяют совместимость формальных и фактических параметров. И если фактические аргументы несовместимы с вызваемой сущностью, компилятор укажет на точку вызова в клиентском коде. Лямбды же, как и обычные функции, нужно обкладывать констрейнтами собственноручно, а это не всегда простая задача. А если констрейнты дырявые (или вообще отсутсвуют), то ошибку придется ловить в потрохах кода и потом самопехом добираться до точки вызова.
Тогда нужно написать нормальный бинд с шахматами и поэтессами.
Чтобы
— нормально подтягивал все ограничения
— поддерживал передачу аргументов явными способами (ну типа, std::ref(_1), std::move(_1), std::move(std::cref(_1)) и прочее) вместо самовольничания
— давал вменяемую диагностику ошибок
— и при этом имел не слишком упоротый синтаксис (буст/стыд — упоротый, чего стоит хотя бы составной бинд)
Ну и получим, либо ещё один буст-феникс какой-нибудь, либо опять вернёмся к лямбдам.
---
Вообще удивительно, ведь именно буст-бинд избавил человечество от самовольной передачи аргументов "наилучшим способом", которую сделал Александреску в Loki (это приводило к висячим ссылкам).
И тут на тебе, вернули гранату.
Ну не undefined behavior (на самом низком уровне — висячие ссылки), а unspecified (приводящее к undefined на уровне пользователя).
Может быть, кинуть багрепорт в комитет стандартизации?
Здравствуйте, Кодт, Вы писали:
К>Тогда нужно написать нормальный бинд с шахматами и поэтессами. К>Чтобы К>- нормально подтягивал все ограничения К>- поддерживал передачу аргументов явными способами (ну типа, std::ref(_1), std::move(_1), std::move(std::cref(_1)) и прочее) вместо самовольничания К>- давал вменяемую диагностику ошибок К>- и при этом имел не слишком упоротый синтаксис (буст/стыд — упоротый, чего стоит хотя бы составной бинд)
К>Ну и получим, либо ещё один буст-феникс какой-нибудь, либо опять вернёмся к лямбдам.
Ну, собственно я это сразу же и сделал — написал свою собственную версию bind по-быстрому. И заняло это аж целых 56 строчек кода, включая deduction guide. Мой бинд, конечно же, не такой общий, как std::bind, зато устраивает меня по всем статьям. Моя версия, как и стандартная, умеет в плейсхолдеры и в композицию. Причем, плейсхолдеры я заюзал прямо те самые, из std. А композицию сделал даже удобнее и логичнее, чем в стандартной библиотеке. Мой bind можно свободно комбинировать с std::bind. Правда мой бинд "проваливается" через их композицию, зато стандартный с моим комбинируется без ограничений. Главное отличие — в моей версии отсутствуе форвардинг — вообще, ибо для меня это просто не актуально, т.к. все заточено под stateless классы функциональных объектов. Не делал также биндинга членов классов — опять же, потому что не актуально. Ну а будет актуально — потрачу минут 15 и сделаю все, что будет нужно.
--
Не можешь достичь желаемого — пожелай достигнутого.
Здравствуйте, rg45, Вы писали:
R>Ну, собственно я это сразу же и сделал — написал свою собственную версию bind по-быстрому.
Есть, где посмотреть в образовательных целях?
_>т.е. тут проверка наличия оператора вызова, но зачем через Inherited?
Ну сейчас же сплошь и рядом шаблонные операторы, у которых адрес просто так не возьмешь — нужно сначала как-то чем-то их инстанцировать. Поэтому берем второй класс, у которого заведомо есть нешаблонный оператор вызова. Наследуемся от этих двух классов и пытаемся взять адрес оператора вызова у наследника. Если это удалось, значит в тестируемом классе нет никаких операторов вызова — ни обычных, ни шаблонных. А если есть хоть какой-нибудь оператор, то возникнет коллизия.
--
Не можешь достичь желаемого — пожелай достигнутого.
Здравствуйте, rg45, Вы писали:
R>Ну сейчас же сплошь и рядом шаблонные операторы, у которых адрес просто так не возьмешь — нужно сначала как-то чем-то их инстанцировать. Поэтому берем второй класс, у которого заведомо есть нешаблонный оператор вызова. Наследуемся от этих двух классов и пытаемся взять адрес оператора вызова у наследника. Если это удалось, значит в тестируемом классе нет никаких операторов вызова — ни обычных, ни шаблонных. А если есть хоть какой-нибудь оператор, то возникнет коллизия.
Здравствуйте, rg45, Вы писали:
R>Ну сейчас же сплошь и рядом шаблонные операторы, у которых адрес просто так не возьмешь — нужно сначала как-то чем-то их инстанцировать. Поэтому берем второй класс, у которого заведомо есть нешаблонный оператор вызова. Наследуемся от этих двух классов и пытаемся взять адрес оператора вызова у наследника. Если это удалось, значит в тестируемом классе нет никаких операторов вызова — ни обычных, ни шаблонных. А если есть хоть какой-нибудь оператор, то возникнет коллизия.