Тут в процессе очередной дискуссии "С++ против всех" родился пример реализации полиморфной (т.е. шаблонной) лямбды в духе 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, *)
// и т.д. для остальных операторов
Здравствуйте, jazzer, Вы писали:
J>Тут в процессе очередной дискуссии "С++ против всех" родился пример реализации полиморфной (т.е. шаблонной) лямбды в духе Boost.Lambda в три строчки на С++11.
GCC 4.8.1 зацикливается при попытке откомпилировать эту лямбду. Воистине, достойный наследник Boost.Lambda!
Здравствуйте, uncommon, Вы писали:
U>Здравствуйте, jazzer, Вы писали:
J>>Тут в процессе очередной дискуссии "С++ против всех" родился пример реализации полиморфной (т.е. шаблонной) лямбды в духе Boost.Lambda в три строчки на С++11.
U>GCC 4.8.1 зацикливается при попытке откомпилировать эту лямбду.
баг компилятора, очевидно. На 4.7 все работает и, вообще говоря, не видно, на чем он тут должен зацикливаться.
Здравствуйте, jazzer, Вы писали:
J>Тут в процессе очередной дискуссии "С++ против всех" родился пример реализации полиморфной (т.е. шаблонной) лямбды в духе Boost.Lambda в три строчки на С++11. J>Решил, что тут тоже многим будет интересно:
Ты там это как-то мимолётом сказал, что я подумал что это давно известная тема. Заострять внимание не стал — так как вокруг были пираньи
А вообще — интересный abuse того, что bind строит expression
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*Gauto 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.
Здравствуйте, jazzer, Вы писали:
J>Ну, от этого очень легко защититься при помощи enable_if, заставив хотя бы один аргумент быть либо плейсхолдером, либо байндом:
Это понятно, вопрос в том почему оригинальный вариант не фейлится.
Здравствуйте, jazzer, Вы писали:
К>>То есть, для ВСЕХ типов, для которых оператор ещё не был определён, — мы его определяем через bind.
J>Ну, от этого очень легко защититься при помощи enable_if, заставив хотя бы один аргумент быть либо плейсхолдером, либо байндом:
Эх. Компилятору от 2013-ой студии от этого enable_if крышу сносит и он вываливается с AV
Здравствуйте, Кодт, Вы писали:
EP>>Это понятно, вопрос в том почему оригинальный вариант не фейлится. К>Но защищаться придётся всё равно, хотя бы ради MSVC
Не, секундочку. В каких случаях шаблон ищет объявления зависимостей в точке определения, а в каких случаях — в точке первого воплощения? Что-то я дезориентирован.
Больше того
#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 его находит!
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, 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, Вы писали:
К>>Здесь определение перегруженной функции находится ниже даже точки воплощения. Однако gcc его находит! J>А вот это странно, ибо
Я тоже так думал. Но эта штука воспроизводится на gcc 4.6, 4.8, VC 2010, 2012.
Здравствуйте, 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, Вы писали:
J>>Так что, начиная с С++11, теперь официально точка инстанцирования может считаться расположенной в конце файла
К>Вот и накрылись медным тазом ремарковские compile-time счётчики инстанцирования...
А там не разные инстанцирования считались?
Плюс, насколько я понимаю, это просто узаконивание существовавшей на тот момент практики компиляторостроителей. Т.е. оно и раньше так было.
Здравствуйте, jazzer, Вы писали:
К>>Вот и накрылись медным тазом ремарковские compile-time счётчики инстанцирования... J>А там не разные инстанцирования считались?
Там фокус был в том, что контекст до инстанцирования и после инстанцирования становился разным.
А если все точки находятся в одном месте — в конце файла, то контекст для них одинаковый.
J>Плюс, насколько я понимаю, это просто узаконивание существовавшей на тот момент практики компиляторостроителей. Т.е. оно и раньше так было.
Эта практика эволюционировала. То gcc придирался к наличию объявлений выше по коду (в соответствии с трактовкой стандарта), то вдруг резко расслабился.
Я ещё понимаю VC, там исторически сложилось расслабленное отношение к шаблонам — как к мегамакросам.
Но с чего вдруг надо было ломать существующее решение?
Не, пускай-пускай, меньше рисков нарваться на глупые трюки.
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, jazzer, Вы писали:
К>>>Вот и накрылись медным тазом ремарковские compile-time счётчики инстанцирования... J>>А там не разные инстанцирования считались?
К>Там фокус был в том, что контекст до инстанцирования и после инстанцирования становился разным. К>А если все точки находятся в одном месте — в конце файла, то контекст для них одинаковый.
Ну относительный порядок у них не отменялся, вроде... Я так надеюсь, то, что инстанцируется раньше, будет менять контекст для того, что позже...
К>Не, пускай-пускай, меньше рисков нарваться на глупые трюки.
Здравствуйте, 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.