Множественный форвардинг в std::bind
От: rg45 СССР  
Дата: 13.03.24 15:58
Оценка: 92 (5) +1
Привет, всем!

Споткнулся о прикольную багу фичу — множественный форвардинг (а значит, и множественное перемещение, иногда) внутри std::bind:

http://coliru.stacked-crooked.com/a/041bd611b4bd1924

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


Почему так сделано — это понятно. Но ошибку искал часа полтора. Так что, будьте осмотрительны, остерегайтесь выходить на болота в ночное время ну, в общем, вы поняли.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re: Множественный форвардинг в std::bind
От: Кодт Россия  
Дата: 14.03.24 15:46
Оценка: +2
Здравствуйте, 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);  // ошибка компиляции, всё честно


А уж какие нечеловекочитаемые ошибки компиляции выводит бинд, по сравнению с лямбдой...
Перекуём баги на фичи!
Re[2]: Множественный форвардинг в std::bind
От: rg45 СССР  
Дата: 14.03.24 15:52
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Вот поэтому всегда лучше вместо std::bind писать лямбды и руками в них делать, что хочешь.

К>Это и нагляднее, и меньше внезапных спецэффектов.

Да, в принципе, согласен. Но есть нюанс, как обычно. Плюс std::bind в том, что он генерует вызываемую сущность, уже обложенную констрейнтами, которые проверяют совместимость формальных и фактических параметров. И если фактические аргументы несовместимы с вызваемой сущностью, компилятор укажет на точку вызова в клиентском коде. Лямбды же, как и обычные функции, нужно обкладывать констрейнтами собственноручно, а это не всегда простая задача. А если констрейнты дырявые (или вообще отсутсвуют), то ошибку придется ловить в потрохах кода и потом самопехом добираться до точки вызова.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 14.03.2024 16:07 rg45 . Предыдущая версия . Еще …
Отредактировано 14.03.2024 15:53 rg45 . Предыдущая версия .
Re[3]: Множественный форвардинг в std::bind
От: Кодт Россия  
Дата: 19.03.24 13:10
Оценка: +1
Здравствуйте, rg45, Вы писали:

R>Да, в принципе, согласен. Но есть нюанс, как обычно. Плюс std::bind в том, что он генерует вызываемую сущность, уже обложенную констрейнтами, которые проверяют совместимость формальных и фактических параметров. И если фактические аргументы несовместимы с вызваемой сущностью, компилятор укажет на точку вызова в клиентском коде. Лямбды же, как и обычные функции, нужно обкладывать констрейнтами собственноручно, а это не всегда простая задача. А если констрейнты дырявые (или вообще отсутсвуют), то ошибку придется ловить в потрохах кода и потом самопехом добираться до точки вызова.


Тогда нужно написать нормальный бинд с шахматами и поэтессами.
Чтобы
— нормально подтягивал все ограничения
— поддерживал передачу аргументов явными способами (ну типа, std::ref(_1), std::move(_1), std::move(std::cref(_1)) и прочее) вместо самовольничания
— давал вменяемую диагностику ошибок
— и при этом имел не слишком упоротый синтаксис (буст/стыд — упоротый, чего стоит хотя бы составной бинд)

Ну и получим, либо ещё один буст-феникс какой-нибудь, либо опять вернёмся к лямбдам.

---

Вообще удивительно, ведь именно буст-бинд избавил человечество от самовольной передачи аргументов "наилучшим способом", которую сделал Александреску в Loki (это приводило к висячим ссылкам).
И тут на тебе, вернули гранату.
Ну не undefined behavior (на самом низком уровне — висячие ссылки), а unspecified (приводящее к undefined на уровне пользователя).

Может быть, кинуть багрепорт в комитет стандартизации?
Перекуём баги на фичи!
Re[4]: Множественный форвардинг в std::bind
От: rg45 СССР  
Дата: 19.03.24 22:51
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Тогда нужно написать нормальный бинд с шахматами и поэтессами.

К>Чтобы
К>- нормально подтягивал все ограничения
К>- поддерживал передачу аргументов явными способами (ну типа, std::ref(_1), std::move(_1), std::move(std::cref(_1)) и прочее) вместо самовольничания
К>- давал вменяемую диагностику ошибок
К>- и при этом имел не слишком упоротый синтаксис (буст/стыд — упоротый, чего стоит хотя бы составной бинд)

К>Ну и получим, либо ещё один буст-феникс какой-нибудь, либо опять вернёмся к лямбдам.


Ну, собственно я это сразу же и сделал — написал свою собственную версию bind по-быстрому. И заняло это аж целых 56 строчек кода, включая deduction guide. Мой бинд, конечно же, не такой общий, как std::bind, зато устраивает меня по всем статьям. Моя версия, как и стандартная, умеет в плейсхолдеры и в композицию. Причем, плейсхолдеры я заюзал прямо те самые, из std. А композицию сделал даже удобнее и логичнее, чем в стандартной библиотеке. Мой bind можно свободно комбинировать с std::bind. Правда мой бинд "проваливается" через их композицию, зато стандартный с моим комбинируется без ограничений. Главное отличие — в моей версии отсутствуе форвардинг — вообще, ибо для меня это просто не актуально, т.к. все заточено под stateless классы функциональных объектов. Не делал также биндинга членов классов — опять же, потому что не актуально. Ну а будет актуально — потрачу минут 15 и сделаю все, что будет нужно.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[5]: Множественный форвардинг в std::bind
От: serg_joker Украина  
Дата: 20.03.24 16:35
Оценка:
Здравствуйте, rg45, Вы писали:

R>Ну, собственно я это сразу же и сделал — написал свою собственную версию bind по-быстрому.

Есть, где посмотреть в образовательных целях?
Re[6]: Множественный форвардинг в std::bind
От: rg45 СССР  
Дата: 21.03.24 16:17
Оценка: 43 (3)
Здравствуйте, serg_joker, Вы писали:

_>Есть, где посмотреть в образовательных целях?


Всегда пожалуйста!

http://coliru.stacked-crooked.com/a/773ea192115f80cf

#include <concepts>
#include <functional>
#include <iostream>
#include <sstream>
#include <utility>

namespace util
{

template <typename...T>
struct Inherited : std::decay_t<T>... {};

template <typename> struct IsFreeFunction : std::false_type {};
template<typename R, typename...A> struct IsFreeFunction<R(A...)> : std::true_type {};
template <typename T> struct IsFreeFunction<T*> : IsFreeFunction<T> {};

template <typename T>
concept FreeFunction = IsFreeFunction<std::decay_t<T>>::value;

template <typename T>
concept FunctionObject =
std::is_class_v<std::decay_t<T>> &&
!requires { &Inherited < T, decltype([] {}) > ::operator(); };

template <typename T>
concept Function = FreeFunction<T> || FunctionObject<T>;

template <typename T>
concept Entity = !Function<T> && !std::same_as<T, void>;

using std::placeholders::_1;
using std::placeholders::_2;
using std::placeholders::_3;
using std::placeholders::_4;
using std::placeholders::_5;
using std::placeholders::_6;
using std::placeholders::_7;
using std::placeholders::_8;
using std::placeholders::_9;
using std::placeholders::_10;
using std::placeholders::_11;
using std::placeholders::_12;

template <typename> struct PlaceholderIndexTag;
template <> struct PlaceholderIndexTag<std::decay_t<decltype(_1)>> : std::integral_constant<size_t, 0> {};
template <> struct PlaceholderIndexTag<std::decay_t<decltype(_2)>> : std::integral_constant<size_t, 1> {};
template <> struct PlaceholderIndexTag<std::decay_t<decltype(_3)>> : std::integral_constant<size_t, 2> {};
template <> struct PlaceholderIndexTag<std::decay_t<decltype(_4)>> : std::integral_constant<size_t, 3> {};
template <> struct PlaceholderIndexTag<std::decay_t<decltype(_5)>> : std::integral_constant<size_t, 4> {};
template <> struct PlaceholderIndexTag<std::decay_t<decltype(_6)>> : std::integral_constant<size_t, 5> {};
template <> struct PlaceholderIndexTag<std::decay_t<decltype(_7)>> : std::integral_constant<size_t, 6> {};
template <> struct PlaceholderIndexTag<std::decay_t<decltype(_8)>> : std::integral_constant<size_t, 7> {};
template <> struct PlaceholderIndexTag<std::decay_t<decltype(_9)>> : std::integral_constant<size_t, 8> {};
template <> struct PlaceholderIndexTag<std::decay_t<decltype(_10)>> : std::integral_constant<size_t, 9> {};
template <> struct PlaceholderIndexTag<std::decay_t<decltype(_11)>> : std::integral_constant<size_t, 10> {};
template <> struct PlaceholderIndexTag<std::decay_t<decltype(_12)>> : std::integral_constant<size_t, 11> {};

template <typename T>
concept Placeholder = (Entity<T> || Function<T>) && requires {
   PlaceholderIndexTag<std::decay_t<T>>::value;
};

template <Placeholder T>
inline constexpr size_t PlaceholderIndex = PlaceholderIndexTag<std::decay_t<T>>::value;

template <Function F, typename...X>
class Bind
{
public:
   constexpr Bind() = default;

   template <Function FF, typename...XX>
   constexpr Bind(FF&& f, XX&&...x) : m_f(std::forward<FF>(f)), m_x(std::forward<XX>(x)...){}

   template <typename...T>
   constexpr decltype(auto) operator()(T&&...t) const
   requires requires {this->invoke(std::index_sequence_for<X...>{}, std::forward<T>(t)...); }
   {
      return invoke(std::index_sequence_for<X...>{}, std::forward<T>(t)...);
   }

private:

   template <Entity FF, typename...T>
   static constexpr FF expand(FF&& f, T&&...)
   requires requires {std::forward<FF>(f);}
   {
      return std::forward<FF>(f);
   }

   template <Function FF, typename...T>
   static constexpr decltype(auto) expand(FF&& f, T&&...t)
   requires requires {f(std::forward<T>(t)...); }
   {
      return f(std::forward<T>(t)...);
   }

   template <Placeholder P, typename...T>
   static constexpr decltype(auto) expand(const P&, T&&...t)
   requires (PlaceholderIndex<P> < sizeof...(T))
   {
      return std::get<PlaceholderIndex<P>>(std::forward_as_tuple(std::forward<T>(t)...));
   }

   template <size_t...I, typename...T>
   constexpr decltype(auto) invoke(std::index_sequence<I...>, T&&...t) const
   requires requires {this->m_f(this->expand(std::get<I>(this->m_x), std::forward<T>(t)...)...);}
   {
      return m_f(expand(std::get<I>(m_x), std::forward<T>(t)...)...);
   }

private:
   F m_f{};
   std::tuple<X...> m_x;
};

template <Function F, typename...X>
Bind(F&&, X&&...) -> Bind<std::decay_t<F>, std::decay_t<X>...>;

} // namespace util


int main()
{
   using namespace std::placeholders;
   using util::Bind;

   constexpr auto make = [](auto&& t, auto&&...x) {
      std::ostringstream os;
      os << "{" << t;
      ((os << ", " << x), ...);
      os << "}";
      return os.str();
   };

   std::cout << Bind(make, make, Bind(make, make, 42, make), make)("A", 3.14) << std::endl;
   std::cout << Bind(make, _2, _3, _1, Bind(make, _2, _3, _1, _1, _3, _2), _1, _3, _2)("A", 42, 3.14) << std::endl;
}
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 21.03.2024 16:31 rg45 . Предыдущая версия .
Re[7]: Множественный форвардинг в std::bind
От: serg_joker Украина  
Дата: 21.03.24 17:11
Оценка:
Здравствуйте, rg45, Вы писали:

R>Всегда пожалуйста!

Спасибо!

Я не совсем понимаю эту часть:
template <typename...T>
struct Inherited : std::decay_t<T>... {};

template <typename T>
concept FunctionObject =
std::is_class_v<std::decay_t<T>> &&
!requires { &Inherited < T, decltype([] {}) > ::operator(); };


т.е. тут проверка наличия оператора вызова, но зачем через Inherited?
Отредактировано 21.03.2024 17:14 serg_joker . Предыдущая версия .
Re[8]: Множественный форвардинг в std::bind
От: rg45 СССР  
Дата: 21.03.24 17:28
Оценка: 112 (3)
Здравствуйте, serg_joker, Вы писали:

_>Я не совсем понимаю эту часть:

_>
_>template <typename...T>
_>struct Inherited : std::decay_t<T>... {};

_>template <typename T>
_>concept FunctionObject =
_>std::is_class_v<std::decay_t<T>> &&
_>!requires { &Inherited < T, decltype([] {}) > ::operator(); };
_>


_>т.е. тут проверка наличия оператора вызова, но зачем через Inherited?


Ну сейчас же сплошь и рядом шаблонные операторы, у которых адрес просто так не возьмешь — нужно сначала как-то чем-то их инстанцировать. Поэтому берем второй класс, у которого заведомо есть нешаблонный оператор вызова. Наследуемся от этих двух классов и пытаемся взять адрес оператора вызова у наследника. Если это удалось, значит в тестируемом классе нет никаких операторов вызова — ни обычных, ни шаблонных. А если есть хоть какой-нибудь оператор, то возникнет коллизия.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[9]: Множественный форвардинг в std::bind
От: serg_joker Украина  
Дата: 21.03.24 17:37
Оценка:
Здравствуйте, rg45, Вы писали:

R>Ну сейчас же сплошь и рядом шаблонные операторы, у которых адрес просто так не возьмешь — нужно сначала как-то чем-то их инстанцировать. Поэтому берем второй класс, у которого заведомо есть нешаблонный оператор вызова. Наследуемся от этих двух классов и пытаемся взять адрес оператора вызова у наследника. Если это удалось, значит в тестируемом классе нет никаких операторов вызова — ни обычных, ни шаблонных. А если есть хоть какой-нибудь оператор, то возникнет коллизия.


Понял, спасибо!
Re[9]: Множественный форвардинг в std::bind
От: Кодт Россия  
Дата: 29.03.24 23:58
Оценка: :)
Здравствуйте, rg45, Вы писали:

R>Ну сейчас же сплошь и рядом шаблонные операторы, у которых адрес просто так не возьмешь — нужно сначала как-то чем-то их инстанцировать. Поэтому берем второй класс, у которого заведомо есть нешаблонный оператор вызова. Наследуемся от этих двух классов и пытаемся взять адрес оператора вызова у наследника. Если это удалось, значит в тестируемом классе нет никаких операторов вызова — ни обычных, ни шаблонных. А если есть хоть какой-нибудь оператор, то возникнет коллизия.


Злое колдунство!
Перекуём баги на фичи!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.