почти Boost.Lambda в C++11
От: jazzer Россия Skype: enerjazzer
Дата: 30.10.13 03:11
Оценка: 76 (9) :)
Тут в процессе очередной дискуссии "С++ против всех" родился пример реализации полиморфной (т.е. шаблонной) лямбды в духе Boost.Lambda в три строчки на С++11.
Решил, что тут тоже многим будет интересно:
#include <functional>
#define JUST_RETURN(...) decltype( __VA_ARGS__ ) { return __VA_ARGS__; }
#define OP(name,op) struct name {                                            \
  template<class X, class Y>auto operator()(X x, Y y) -> JUST_RETURN(x op y) \
};                                                                           \
template<class X, class Y>auto operator op(X x, Y y) -> JUST_RETURN(std::bind(name(), x, y))

OP(Mul, *)
// и т.д. для остальных операторов

  полный код с тестом
#include <functional>
#include <iostream>
#include <string> // for "Hello, world!"

#define JUST_RETURN(...) decltype( __VA_ARGS__ ) { return __VA_ARGS__; }
#define OP(name,op) struct name {                                            \
  template<class X, class Y>auto operator()(X x, Y y) -> JUST_RETURN(x op y) \
};                                                                           \
template<class X, class Y>auto operator op(X x, Y y) -> JUST_RETURN(std::bind(name(), x, y))

OP(Mul, *)
OP(Div, /)
OP(Mod, %)
OP(Add, +)
OP(Sub, -)
OP(EQ, ==)
OP(LT, < )
OP(LE, <=)
OP(GT, > )
OP(GE, >=)

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

  cout << ( 9 * _1)(5)          << endl;                     // 45
  cout << (_1 *  8)(5)          << endl;                     // 40
  cout << (_1 * _1)(5)          << endl;                     // 25
  cout << (_1 * _2)(5,6)        << endl;                     // 30
  cout << (_1 + _2 * _3)(5,6,7) << endl;                     // 47 (42+5)
  cout << (_3 - _2 * _1)(5,6,7) << endl;                     // -23 (7-30)
  cout << ( 9 * _2)(5,6)        << endl;                     // 54
  cout << (_2+", "+_1+"!")("world",string("Hello")) << endl; // "Hello, world!"
}
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re: почти Boost.Lambda в C++11
От: uncommon Ниоткуда  
Дата: 30.10.13 03:41
Оценка: :))) :))
Здравствуйте, jazzer, Вы писали:

J>Тут в процессе очередной дискуссии "С++ против всех" родился пример реализации полиморфной (т.е. шаблонной) лямбды в духе Boost.Lambda в три строчки на С++11.


GCC 4.8.1 зацикливается при попытке откомпилировать эту лямбду. Воистине, достойный наследник Boost.Lambda!
Re[2]: почти Boost.Lambda в C++11
От: jazzer Россия Skype: enerjazzer
Дата: 30.10.13 04:00
Оценка: +1
Здравствуйте, uncommon, Вы писали:

U>Здравствуйте, jazzer, Вы писали:


J>>Тут в процессе очередной дискуссии "С++ против всех" родился пример реализации полиморфной (т.е. шаблонной) лямбды в духе Boost.Lambda в три строчки на С++11.


U>GCC 4.8.1 зацикливается при попытке откомпилировать эту лямбду.


баг компилятора, очевидно. На 4.7 все работает и, вообще говоря, не видно, на чем он тут должен зацикливаться.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re: почти Boost.Lambda в C++11
От: Evgeny.Panasyuk Россия  
Дата: 30.10.13 06:30
Оценка:
Здравствуйте, jazzer, Вы писали:

J>Тут в процессе очередной дискуссии "С++ против всех" родился пример реализации полиморфной (т.е. шаблонной) лямбды в духе Boost.Lambda в три строчки на С++11.

J>Решил, что тут тоже многим будет интересно:

Ты там это как-то мимолётом сказал, что я подумал что это давно известная тема. Заострять внимание не стал — так как вокруг были пираньи
А вообще — интересный abuse того, что bind строит expression
Re: почти Boost.Lambda в C++11
От: Кодт Россия  
Дата: 30.10.13 08:00
Оценка:
Здравствуйте, jazzer, Вы писали:

J>#include <functional>
J>#define JUST_RETURN(...) decltype( __VA_ARGS__ ) { return __VA_ARGS__; }
J>#define OP(name,op) struct name {                                            \
J>  template<class X, class Y>auto operator()(X x, Y y) -> JUST_RETURN(x op y) \
J>};                                                                           \
J>template<class X, class Y>auto operator op(X x, Y y) -> JUST_RETURN(std::bind(name(), x, y))


Вот я что-то не понял! Получается
struct Mul
{
  template<class X, class Y>
  auto operator () (X x, Y y) const -> decltype(x*y) // у тебя в определении пропущено
  { return x*y; }
};

template<class X, class Y>
auto operator * (X x, Y y) -> decltype(std::bind(Mul(),x,y))
{ return std::bind(Mul(),x,y); }

То есть, для ВСЕХ типов, для которых оператор ещё не был определён, — мы его определяем через bind.
struct F {};
struct G {};

auto fog = F() * G(); // странность: bind(Mul(),F(),G()) - мы точно этого хотели?
auto letsgo = fog();  // смысловая ошибка: в точке определения Mul не было оператора F*G
auto gogogo = fog(1)(1,2)(1,2,3); // по-прежнему bind(Mul(),F(),G())

Я ожидал, что в gcc (проверял на 4.6.3 — cygwin) здесь произойдёт SFINAE: поскольку выше определения Mul нет объявления оператора F*G, то шаблон operator() не совпадёт, а bind, соответственно, сломается. И это будет хоть какая-то диагностика...
Однако, как и VC2012 (для которого такое поведение привычно), он нашёл шаблон X*Y, поэтому F*G возвращает bind, а bind(any-garbage-args) возвращает Mul(F,G) = F*G = bind.
Перекуём баги на фичи!
Re[2]: почти Boost.Lambda в C++11
От: jazzer Россия Skype: enerjazzer
Дата: 30.10.13 08:28
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Здравствуйте, jazzer, Вы писали:


К>
                                                                         \
J>>template<class X, class Y>auto operator op(X x, Y y) -> JUST_RETURN(std::bind(name(), x, y))
К>


К>Вот я что-то не понял! Получается

К>
К>template<class X, class Y>
К>auto operator * (X x, Y y) -> decltype(std::bind(Mul(),x,y))
К>{ return std::bind(Mul(),x,y); }
К>

К>То есть, для ВСЕХ типов, для которых оператор ещё не был определён, — мы его определяем через bind.

Ну, от этого очень легко защититься при помощи enable_if, заставив хотя бы один аргумент быть либо плейсхолдером, либо байндом:
template<class X, class Y>auto operator op(X x, Y y) \
-> typename std::enable_if<                          \
        std::is_placeholder<X>::value                \
     || std::is_placeholder<Y>::value                \
     || std::is_bind_expression<X>::value            \
     || std::is_bind_expression<Y>::value            \
   , decltype(std::bind(name(), x, y))               \
   >::type                                           \
{ return std::bind(name(), x, y); }
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[3]: почти Boost.Lambda в C++11
От: Evgeny.Panasyuk Россия  
Дата: 30.10.13 08:30
Оценка:
Здравствуйте, jazzer, Вы писали:

J>Ну, от этого очень легко защититься при помощи enable_if, заставив хотя бы один аргумент быть либо плейсхолдером, либо байндом:


Это понятно, вопрос в том почему оригинальный вариант не фейлится.
Re[4]: почти Boost.Lambda в C++11
От: Кодт Россия  
Дата: 30.10.13 08:33
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>Это понятно, вопрос в том почему оригинальный вариант не фейлится.


Но защищаться придётся всё равно, хотя бы ради MSVC
Перекуём баги на фичи!
Re[3]: почти Boost.Lambda в C++11
От: ArtDenis Россия  
Дата: 30.10.13 08:39
Оценка:
Здравствуйте, jazzer, Вы писали:

К>>То есть, для ВСЕХ типов, для которых оператор ещё не был определён, — мы его определяем через bind.


J>Ну, от этого очень легко защититься при помощи enable_if, заставив хотя бы один аргумент быть либо плейсхолдером, либо байндом:


Эх. Компилятору от 2013-ой студии от этого enable_if крышу сносит и он вываливается с AV
[ 🎯 Дартс-лига Уфы | 🌙 Программа для сложения астрофото ]
Re[5]: почти Boost.Lambda в C++11
От: Evgeny.Panasyuk Россия  
Дата: 30.10.13 08:40
Оценка:
Здравствуйте, Кодт, Вы писали:

EP>>Это понятно, вопрос в том почему оригинальный вариант не фейлится.

К>Но защищаться придётся всё равно, хотя бы ради MSVC

Мне кажется оно и не должно фейлится: http://coliru.stacked-crooked.com/a/e5030c7a754d1989
Определение внешнего operator* уже есть в точке instantiation.
Re[6]: почти Boost.Lambda в C++11
От: Кодт Россия  
Дата: 30.10.13 10:01
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>Мне кажется оно и не должно фейлится: http://coliru.stacked-crooked.com/a/e5030c7a754d1989

EP>Определение внешнего operator* уже есть в точке instantiation.

Не, секундочку. В каких случаях шаблон ищет объявления зависимостей в точке определения, а в каких случаях — в точке первого воплощения? Что-то я дезориентирован.

Больше того
#include <cstdio>

#define WHOAMI() printf("%s\n", __PRETTY_FUNCTION__)

template<class X, class Y>
int foo(X x, Y y)
{
    WHOAMI();
    return 1;
}

template<class X, class Y>
auto bar(X x, Y y) -> decltype(foo(x,y))
{
    WHOAMI();
    return foo(x,y);
}

struct F {};
struct G {};

int main()
{
    bar(F(),G());
}

#if 1 // вкл-выкл
float foo(F f, G g)
{
    WHOAMI();
    return 1.f;
}
#endif

Здесь определение перегруженной функции находится ниже даже точки воплощения. Однако gcc его находит!
Перекуём баги на фичи!
Re[7]: почти Boost.Lambda в C++11
От: jazzer Россия Skype: enerjazzer
Дата: 30.10.13 10:26
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Здравствуйте, Evgeny.Panasyuk, Вы писали:


EP>>Мне кажется оно и не должно фейлится: http://coliru.stacked-crooked.com/a/e5030c7a754d1989

EP>>Определение внешнего operator* уже есть в точке instantiation.

К>Не, секундочку. В каких случаях шаблон ищет объявления зависимостей в точке определения, а в каких случаях — в точке первого воплощения? Что-то я дезориентирован.


Зависимые имена всегда ищутся в точке воплощения.

Such (dependent — jazzer) names are unbound and are looked up at the point of the template instantiation (14.6.4.1) in both the
context of the template definition and the context of the point of instantiation.


К>Здесь определение перегруженной функции находится ниже даже точки воплощения. Однако gcc его находит!


А вот это странно, ибо

The instantiation context of an expression that depends on the template arguments is the set of declarations
with external linkage declared prior to the point of instantiation of the template specialization in the same
translation unit.

jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[8]: почти Boost.Lambda в C++11
От: Кодт Россия  
Дата: 30.10.13 12:45
Оценка:
Здравствуйте, jazzer, Вы писали:

К>>Здесь определение перегруженной функции находится ниже даже точки воплощения. Однако gcc его находит!

J>А вот это странно, ибо

Я тоже так думал. Но эта штука воспроизводится на gcc 4.6, 4.8, VC 2010, 2012.

Вообще, минимальный код http://ideone.com/NPa7we
#include <iostream>
using namespace std;

template<class T> void bar(T t) { foo(t); }

struct F {};

int main() {
    F f;
    bar(f);
    return 0;
}

void foo(F) { cout << "foo(F)"; }
Перекуём баги на фичи!
Re[9]: почти Boost.Lambda в C++11
От: Evgeny.Panasyuk Россия  
Дата: 30.10.13 12:48
Оценка:
Здравствуйте, Кодт, Вы писали:


К>Я тоже так думал. Но эта штука воспроизводится на gcc 4.6, 4.8, VC 2010, 2012.


+ Clang
Re[10]: почти Boost.Lambda в C++11
От: jazzer Россия Skype: enerjazzer
Дата: 31.10.13 03:16
Оценка: 44 (5)
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>Здравствуйте, Кодт, Вы писали:



К>>Я тоже так думал. Но эта штука воспроизводится на gcc 4.6, 4.8, VC 2010, 2012.


EP>+ Clang


Как обычно, исследование С++ — это детектив

В коде LLVM есть замечательный комментарий, имхо, применимый здесь напрямую:

// FIXME: When we perform these implicit instantiations, we do not carefully
// keep track of the point of instantiation (C++ [temp.point]). This means
// that name lookup that occurs within the template instantiation will
// always happen at the end of the translation unit, so it will find
// some names that should not be found. Although this is common behavior
// for C++ compilers, it is technically wrong.
In the future, we either need
// to be able to filter the results of name lookup or we need to perform
// template instantiations earlier.


Это комментарий 2009 года.
В том же 2009 появился дефект 993 "Freedom to perform instantiation at the end of the translation unit"
http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#993
И уже через 2 недели (что намекает на то, что действительно все компиляторы этим занимаются) в С++11 были приняты соответствующие изменения в [temp.point] (параграф 8 в текущем драфте, был 7 в С++03)

A specialization for a function template, a member function template, or of a member function or static
data member of a class template may have multiple points of instantiations within a translation unit, and
in addition to the points of instantiation described above

(типа правила the point of instantiation for such a specialization immediately follows the namespace scope declaration or definition that refers to the specialization — jazzer),
for any such specialization that has a point
of instantiation within the translation unit, the end of the translation unit is also considered a point of
instantiation.
A specialization for a class template has at most one point of instantiation within a translation
unit. A specialization for any template may have points of instantiation in multiple translation units. If
two different points of instantiation give a template specialization different meanings according to the one
definition rule (3.2), the program is ill-formed, no diagnostic required.


Так что, начиная с С++11, теперь официально точка инстанцирования может считаться расположенной в конце файла
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[11]: почти Boost.Lambda в C++11
От: Кодт Россия  
Дата: 31.10.13 09:20
Оценка:
Здравствуйте, jazzer, Вы писали:

J>Так что, начиная с С++11, теперь официально точка инстанцирования может считаться расположенной в конце файла


Вот и накрылись медным тазом ремарковские compile-time счётчики инстанцирования...
Перекуём баги на фичи!
Re[12]: почти Boost.Lambda в C++11
От: jazzer Россия Skype: enerjazzer
Дата: 31.10.13 10:19
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Здравствуйте, jazzer, Вы писали:


J>>Так что, начиная с С++11, теперь официально точка инстанцирования может считаться расположенной в конце файла


К>Вот и накрылись медным тазом ремарковские compile-time счётчики инстанцирования...

А там не разные инстанцирования считались?

Плюс, насколько я понимаю, это просто узаконивание существовавшей на тот момент практики компиляторостроителей. Т.е. оно и раньше так было.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[13]: почти Boost.Lambda в C++11
От: Кодт Россия  
Дата: 31.10.13 12:24
Оценка:
Здравствуйте, jazzer, Вы писали:

К>>Вот и накрылись медным тазом ремарковские compile-time счётчики инстанцирования...

J>А там не разные инстанцирования считались?

Там фокус был в том, что контекст до инстанцирования и после инстанцирования становился разным.
А если все точки находятся в одном месте — в конце файла, то контекст для них одинаковый.

J>Плюс, насколько я понимаю, это просто узаконивание существовавшей на тот момент практики компиляторостроителей. Т.е. оно и раньше так было.


Эта практика эволюционировала. То gcc придирался к наличию объявлений выше по коду (в соответствии с трактовкой стандарта), то вдруг резко расслабился.
Я ещё понимаю VC, там исторически сложилось расслабленное отношение к шаблонам — как к мегамакросам.
Но с чего вдруг надо было ломать существующее решение?

Не, пускай-пускай, меньше рисков нарваться на глупые трюки.
Перекуём баги на фичи!
Re[14]: почти Boost.Lambda в C++11
От: jazzer Россия Skype: enerjazzer
Дата: 31.10.13 14:30
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Здравствуйте, jazzer, Вы писали:


К>>>Вот и накрылись медным тазом ремарковские compile-time счётчики инстанцирования...

J>>А там не разные инстанцирования считались?

К>Там фокус был в том, что контекст до инстанцирования и после инстанцирования становился разным.

К>А если все точки находятся в одном месте — в конце файла, то контекст для них одинаковый.

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

К>Не, пускай-пускай, меньше рисков нарваться на глупые трюки.


+1
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[8]: почти Boost.Lambda в C++11
От: Evgeny.Panasyuk Россия  
Дата: 07.11.13 21:56
Оценка:
Здравствуйте, jazzer, Вы писали:

К>>Не, секундочку. В каких случаях шаблон ищет объявления зависимостей в точке определения, а в каких случаях — в точке первого воплощения? Что-то я дезориентирован.

J>Зависимые имена всегда ищутся в точке воплощения.
J>

J>Such (dependent — jazzer) names are unbound and are looked up at the point of the template instantiation (14.6.4.1) in both the
J>context of the template definition and the context of the point of instantiation.


Насколько я понял, эта цитата относится к тому когда делается lookup, а не где он ищет имена.

J>

J>The instantiation context of an expression that depends on the template arguments is the set of declarations
J>with external linkage declared prior to the point of instantiation of the template specialization in the same
J>translation unit.


Это определяет instantiation context, но не то для чего именно он используется
А используется он для:

14.6.4 Dependent name resolution [temp.dep.res]
In resolving dependent names, names from the following sources are considered:
— Declarations that are visible at the point of definition of the template.
— Declarations from namespaces associated with the types of the function arguments both from the instantiation context (14.6.4.1) and from the definition context.

А разница проявляется тут:
#include <iostream>
using namespace std;

template<class T> void bar(T t)
{
    foo(t);
}

struct F {};

#if 1 // Change It
    typedef int T;
#else
    typedef F T;
#endif

void foo(T)
{
    cout << "foo(T)" << endl;
}

int main() {
    T f;
    bar(f);
}
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.