// прямые шаблоны typedef'овtemplate<template<class>class F, class X> using apply = F<X>;
template<template<class>class F, class X, class Y> using apply2 = F<X>;
// старые добрые метафункции с зависимыми именамиtemplate<template<class>class F> struct apply_t { template<class X> using fun = F<X>; };
template<template<class>class F, class Z> struct apply2_t { template<class X> using fun = F<X>; };
// вывернем аргументом наружу, мясом внутрьtemplate<class X> struct pass_t { template<template<class>class F> using fun = F<X>; };
// замаскируем метафункциюtemplate<class Z> struct mask_t { template<template<class>class F, class X> using fun = F<X>; };
// особенно вот эта, в стиле С++98 (только using на typedef переделать)template<template<class>class F, class X> struct applied_t { using result = F<X>; };
template<class... Args> struct run;
template<class... Args> struct run<Args...> // это важно! сбоит только на специализации, пусть даже и совпадающей с генеральным шаблоном
{
template<class X> using one = X;
// любая из этих строк делает ICEtemplate<class X> using ice1 = one<X>; // вот причина айса - все остальные случаи сводятся к немуtemplate<class X> using ice2 = apply<one,X>;
template<class X> using ice3 = typename apply_t <one >::template fun<X>; // apply_t не зависит от Xtemplate<class X> using ice4 = typename apply2_t <one,int>::template fun<X>; // apply2_t не зависит от X
// а эти строчки - не делаютtemplate<class X> using yes1 = typename apply2_t <one,X**>::template fun<X>; // apply2_t зависит от Xtemplate<class X> using yes2 = typename pass_t<X>::template fun<one>;
template<class X> using yes3 = typename mask_t<X**>::template fun<one,X>;
template<class X> using yes4 = typename applied_t<one,X>::result;
};
VS2013 — 18.00.30723 — ICE
VS2015 — 19.00.22620 — компилируется (и даже делает то, что нужно)
Обратите внимание на ice3, ice4 и yes1. Выглядят почти одинаково, а выстреливают по-разному.
При том, что в VS2015 баг исправлен, — но вдруг кто-то пользуется VS2013 и страдает от айса в каких-то схожих местах.
Решение, как мы видим, — в том, чтобы параметризовать используемый шаблон класса-метафункции параметром using'а.
Компилятор при этом думает "а вдруг существует специализация метафункции?" и не пытается подставить её раньше времени.
template<class... Args> struct run;
template<class... Args> struct run<Args...> // это важно! сбоит только на специализации, пусть даже и совпадающей с генеральным шаблоном
Это некорректный код. См. абзац 14.5.4/9 стандарта C++03, абзац 14.5.5/8 стандартов C++11/14:
The argument list of the specialization shall not be identical to the implicit argument list of the primary template.
clang 3.5.0 и g++ 4.9.2 его не пропускают.
К>VS2013 — 18.00.30723 — ICE
Почему-то не удалось добиться появления ICE на VS2013. Не могли бы Вы привести код целиком?
Здравствуйте, Constructor, Вы писали:
C>Здравствуйте, Кодт, Вы писали:
C>
C>template<class... Args> struct run;
C>template<class... Args> struct run<Args...> // это важно! сбоит только на специализации, пусть даже и совпадающей с генеральным шаблоном
C>
Это не имеет значения. Можно любую специализацию, сколь угодно валидную. Лишь бы с вариадиком.
Например,
template<class... Args> struct run<int,Args...> {
C>clang 3.5.0 и g++ 4.9.2 его не пропускают.
Тем более, компилятор мог бы выдать диагностику.
К>>VS2013 — 18.00.30723 — ICE
C>Почему-то не удалось добиться появления ICE на VS2013. Не могли бы Вы привести код целиком?
Это весь код целиком.
Чтобы накренилось, нужно оставить любую строчку using iceN
Платформо-зависимых типов здесь нет, поэтому выбор платформы (x86, x64) не влияет.
Версию компилятора я указал.
Здравствуйте, Кодт, Вы писали:
C>>Почему-то не удалось добиться появления ICE на VS2013. Не могли бы Вы привести код целиком?
К>Это весь код целиком. К>Чтобы накренилось, нужно оставить любую строчку using iceN
К>Платформо-зависимых типов здесь нет, поэтому выбор платформы (x86, x64) не влияет. К>Версию компилятора я указал.
Это очень странно, потому что на VS 2013 w/o updates (cl version 18.0.21005.1) и на VS 2013 Update 4 (cl version 18.00.31101) никакого ICE не наблюдается, компилятор ругается только на отсутствие main. А у Вас, насколько я понимаю, VS 2013 Update 3.
К>>Это весь код целиком. К>>Чтобы накренилось, нужно оставить любую строчку using iceN
К>>Платформо-зависимых типов здесь нет, поэтому выбор платформы (x86, x64) не влияет. К>>Версию компилятора я указал.
C>Это очень странно, потому что на VS 2013 w/o updates (cl version 18.0.21005.1) и на VS 2013 Update 4 (cl version 18.00.31101) никакого ICE не наблюдается, компилятор ругается только на отсутствие main. А у Вас, насколько я понимаю, VS 2013 Update 3.
Проверил сегодня на машине с Visual Studio 2013 Update 3. И впрямь, ICE. Это первый случай в моей практике. В RTM-версии все было нормально, Update 3 выдает ICE, в Update 4 опять все хорошо. Кажется, Visual Studio Team чересчур усердно стала развивать проект. Интересно еще проверить Update 2.
Вывод тут один: переходите на Update 4. Правда, неизвестно сколько багов появилось в Update 4 по сравнению с Update 3 (про по меньшей мере один, причем довольно неприятный, я знаю).
Здравствуйте, x-code, Вы писали:
XC>Что нужно сделать, чтобы понять, как этот код работает?
Учиться, учиться и учиться
Этот код работает так.
1. template typedef вводит некую сущность
template<class T> using foo = T*;
template<class T> using bar = T*;
template<class T> using buz = foo<T>;
2. Идентичность таких сущностей — в отличие от шаблонов классов — штука тонкая. (У тех всё просто: равенство квалифицированных имён).
Надо лезть в стандарт смотреть, почему и как, но вот gcc 4.9 считает, что foo == buz, но foo != bar.
3. Идентичность/различность можно наблюдать, параметризуя этими шаблонами другие шаблоны
4. Компилятор реализует идентификацию через уникальные имена линкуемых символов — которые являют собой некий странный хэш от объявления. Это называется декорированными (манглированными) именами. go<foo> получает одно имя, go<bar> другое, go<buz> внезапно то же имя.
5. Длина этого декорированного имени может быть сколь угодно большой — ведь мы можем нагромоздить в параметры шаблона сколь угодно сложную конструкцию.
6. MSVC 2013sp3 в этом месте облажался, причём видно, что они просто срезали угол — все шаблоны одинаковой арности (а, вероятно, и вообще все шаблоны) манглируются строкой "??". Возможно, что у разработчиков стояло //TODO //FIXME реализовать в ближайшей версии, т.е. не успевали к релизу.
А поскольку формат манглированных имён — это мажорное изменение (влекущее за собой несовместимость библиотек), то //TODO, прощёлканное к первому релизу 2013, автоматически переехало на релиз 2015.
Здравствуйте, x-code, Вы писали:
XC>Что нужно сделать, чтобы понять, как этот код работает?
Можно начать с прочтения книги "Шаблоны C++: справочник разработчика" за авторством Вандервурда с Джосаттисом (точнее, Йосуттисом ). Правда, тут имеются некоторые вещи из C++11 (type aliases, alias templates, variadic templates), который данная книга не захватывает по причине почтенного возраста.
Здравствуйте, BulatZiganshin, Вы писали:
XC>>Что нужно сделать, чтобы понять, как этот код работает?
BZ>мне хватило прочесть соотв. разделы на https://isocpp.org/wiki/faq (до этого C++ я знал на уровне стандарта ~91-го года)
Внезапно, C++ был стандартизован только в 1998 г. (ISO/IEC 14882:1998).
Здравствуйте, Кодт, Вы писали:
К>2. Идентичность таких сущностей — в отличие от шаблонов классов — штука тонкая. (У тех всё просто: равенство квалифицированных имён). К>Надо лезть в стандарт смотреть, почему и как, но вот gcc 4.9 считает, что foo == buz, но foo != bar.
Это баг в g++, вероятно. clang 3.5.0 и vc++ 19 (VS2015) считают по-другому, выдавая два нуля (а вот vc++ 18 из VS2013 выдает две единицы). Вот, что написано в стандарте по этому поводу (C++11, 14.5.7/2):
When a template-id refers to the specialization of an alias template, it is equivalent to the associated type obtained by substitution of its template-arguments for the template-parameters in the type-id of the alias template. [ Note: An alias template name is never deduced. — end note ]
Т.е. alias templates действуют аналогично шаблонам классов. Интересно, что на выходе вот такой программкисвятая троица (clang, g++ и vc++) выдает одни только false:
#include <iostream>
#include <type_traits>
template <class T> using foo = T*;
template <class T> using bar = T*;
template <class T> using buz = foo<T>;
template <typename T>
struct Template{};
template <typename T>
using Alias = Template<T>;
template
<
template <typename>
class T1,
template <typename>
class T2
>
struct is_same_template : std::false_type{};
template
<
template <typename>
class T
>
struct is_same_template<T, T> : std::true_type{};
#define CHECK_IS_SAME(T1, T2) \
std::cout << #T1 " == " #T2 ": " << is_same_template<T1, T2>::value << std::endl
int main()
{
std::cout << std::boolalpha;
CHECK_IS_SAME(foo, bar);
CHECK_IS_SAME(foo, buz);
CHECK_IS_SAME(bar, buz);
}
К>6. MSVC 2013sp3 в этом месте облажался, причём видно, что они просто срезали угол — все шаблоны одинаковой арности (а, вероятно, и вообще все шаблоны) манглируются строкой "??". Возможно, что у разработчиков стояло //TODO //FIXME реализовать в ближайшей версии, т.е. не успевали к релизу.
Service packs для MSVS 2013 пока не выходили, обходятся update'ами.
К>А поскольку формат манглированных имён — это мажорное изменение (влекущее за собой несовместимость библиотек), то //TODO, прощёлканное к первому релизу 2013, автоматически переехало на релиз 2015.
Но ведь в MSVS 2013 Update 4 все работает (как и работало в RTM версии)!
Здравствуйте, Constructor, Вы писали:
C>Здравствуйте, Кодт, Вы писали:
К>>2. Идентичность таких сущностей — в отличие от шаблонов классов — штука тонкая. (У тех всё просто: равенство квалифицированных имён). К>>Надо лезть в стандарт смотреть, почему и как, но вот gcc 4.9 считает, что foo == buz, но foo != bar.
C>Это баг в g++, вероятно. clang 3.5.0 и vc++ 19 (VS2015) считают по-другому, выдавая два нуля (а вот vc++ 18 из VS2013 выдает две единицы). Вот, что написано в стандарте по этому поводу (C++11, 14.5.7/2): C>
C>When a template-id refers to the specialization of an alias template, it is equivalent to the associated type obtained by substitution of its template-arguments for the template-parameters in the type-id of the alias template. [ Note: An alias template name is never deduced. — end note ]
C>Т.е. alias templates действуют аналогично шаблонам классов.
По-моему, в стандарте есть дефект. Сейчас перечитал его, но так и не понял, как из этого следует определять идентичность alias template.
Там сказано, что компилятор не будет решать уравнения
template<class X, class Y> struct original {};
template<class X> using xx = original<X,X> {};
template<class X, class Y, class Z> using xxx = original<X,X> {};
template<class X, class Y, class Z> using yyy = original<Y,Y> {};
template<template<class>class TT> void foo(TT<int>);
template<template<class,class,class>class TT> void foo(TT<int,int,int>);
original<int,int> oii;
foo(oii); // ну допустим, мы нашли в поле зрения единственное преобразование из TT<int> в original<int,int> - это xx
bar(oii); // а вот здесь уже два - xxx и yyy
в дополнение к известным ограничениям — решение уравнений с зависимыми именами и циклические ссылки зависимых имён друг на друга.
C>Но ведь в MSVS 2013 Update 4 все работает (как и работало в RTM версии)!
Очень интересно!
Перекуём баги на фичи!
Re: [оффтоп] А что делать, если оптимизатор косячит?
Я вот недавно на грабли в 2012 студии наступил. После линковки в релиз попадал заведомо неправильный код. При включенном compile-time optimization. Воспроизвести, минимизировав проект, не удалось, похоже, как-то связано то ли с размером проекта, то ли еще с какой-то чертовщиной. Проблему-то удалось пофиксить малой кровью, volatile в нужном месте творит чудеса, но до сих пор не знаю, что делать. Воскурил официальный ман о том, как репортить баги оптимизатора, но там было английским по белому написано — если у вас бага проявлятся из-за оптимизации при компоновке, вы screwed.
Здравствуйте, Constructor, Вы писали:
C>Внезапно, C++ был стандартизован только в 1998 г. (ISO/IEC 14882:1998).
In 1985, the first edition of The C++ Programming Language was released, which became the definitive reference for the language, as there was not yet an official standard
К>При том, что в VS2015 баг исправлен, — но вдруг кто-то пользуется VS2013 и страдает от айса в каких-то схожих местах.
Я нашёл способ покрешить и VS2015!
ещё не дистиллировал
#include <iostream>
using namespace std;
// макрос для вывода "что я за функция?"#define WHOAMI() (cout << __FUNCSIG__ << endl)
// шаблонные параметры шаблона - 1-3-арные шаблоны#define UNARY template<class>class
#define BINARY template<class,class>class
#define TERNARY template<class,class,class>class// генератор классов для нашей иерархииtemplate<class V> struct define
{
void f(V) { WHOAMI(); }
void g(V) { WHOAMI(); }
void h(V) { WHOAMI(); }
void p() { WHOAMI(); }
};
// создадим базовые классы
// (можно было using A = define<int>, но это сделало бы отладочный вывод ещё менее читаемым)struct A : define<int> {};
struct B : define<char> {};
struct C : define<float> {};
struct D : define<double> {};
// генератор миксина, объявляющего using членов#define DEFINE_USING(mixin, member) \
template<class Base, class X, class Y> struct mixin : Base { using X::member; using Y::member; }; \
template<class Base, class X> struct mixin<Base,X,X> : Base { using X::member; }; \
//endmacro
// перечислим интересующие нас члены
DEFINE_USING(using_f, f)
DEFINE_USING(using_g, g)
DEFINE_USING(using_h, h)
DEFINE_USING(using_p, p)
DEFINE_USING(using_q, q) // добавим один несуществующий член - это же просто имя!
// генератор плоской иерархииtemplate<class... Bases> struct inherit : Bases... {};
// свёртка миксином набора баз (пример левой свёртки)template<TERNARY mixin> struct apply_mixin_l_impl
{
template<class Base, class... Xs> struct run;
template<class Base> struct run<Base>
{
using result = Base;
};
template<class Base, class X> struct run<Base, X>
{
using result = mixin<Base,X,X>;
};
template<class Base, class X, class Y> struct run<Base, X,Y>
{
using result = mixin<Base,X,Y>;
};
template<class Base, class X, class Y, class... Zs> struct run<Base, X,Y,Zs...>
{
// левая свёрткаusing tmp = mixin<Base,X,Y>;
using result = typename run<tmp,tmp,Zs...>::result;
};
};
template<class Base, TERNARY mixin, class... Xs> using apply_mixin_l = typename apply_mixin_l_impl<mixin>::template run<Base,Xs...>::result;
// (пример правой свёртки)template<class Base, TERNARY mixin> struct apply_mixin_r_impl
{
template<class... Xs> struct run // определение для случая <>
{
using result = Base;
};
template<class X> struct run<X>
{
using result = mixin<Base,X,X>;
};
template<class X, class Y> struct run<X,Y>
{
using result = mixin<Base,X,Y>;
};
template<class X, class... Ys> struct run<X,Ys...>
{
using folded = typename run<Ys...>::result;
using result = mixin<folded,X,folded>;
};
};
template<class Base, TERNARY mixin, class... Xs> using apply_mixin_r = typename apply_mixin_r_impl<Base,mixin>::template run<Xs...>::result;
// определимся, что будем использовать левую свёрткуtemplate<class Base, TERNARY mixin, class... Xs> using apply_mixin = apply_mixin_l<Base,mixin,Xs...>;
// пример выборочного доступа к функциямusing E0 = inherit<A,B,C,D>;
using E1 = apply_mixin<E0, using_q>; // у нас нет баз с членом q, но это не должно нам помешать!
//using Efail = apply_mixin<E0, using_q, A>; // попытка сделать using A::q вызовет ошибку компиляцииusing Ep = apply_mixin<E1, using_p, D>; // если заявим несколько баз, получим неоднозначностьusing Ef = apply_mixin<Ep, using_f, A,B,C,D>;
using Efg = apply_mixin<Ef, using_g, A,C>; // выбор между g(int) и g(float)using Efgh = apply_mixin<Efg, using_h, B,D>; // выбор между h(char) и h(double)
// композиция миксиновtemplate<class Base, class X, class Y> struct compose_mixins_impl
{
template<TERNARY... Ms> struct run;
//template<> struct run<> // так нельзя, С++11 и 14 запрещают явную специализациюtemplate<TERNARY... Ms> struct run // поэтому для случая <> сделаем реализацию в основном шаблоне
{
using result = Base;
};
template<TERNARY M> struct run<M>
{
using result = M<Base,X,Y>;
};
template<TERNARY M, TERNARY... Ms> struct run<M,Ms...>
{
using folded = typename run<Ms...>::result;
using result = M<folded,X,Y>;
};
};
template<TERNARY... Ms> struct compose_mixins
{
template<class Base, class X, class Y> using mixin = typename compose_mixins_impl<Base,X,Y>::template run<Ms...>::result;
};
// чтобы дать имя шаблону, приходится указать его параметры и передать их в "вычисленный" шаблонtemplate<class Base, class X, class Y> using using_fgh = typename compose_mixins<using_f, using_g, using_h>::template mixin<Base,X,Y>;
// наследник, в котором все члены вытащены для всех базusing Eall = apply_mixin<Ep, using_fgh, A,B,C,D>;
// наконец, давайте сконструируем всё вместе.
// для этого нам понадобится перечислить сразу два variadic-набораtemplate<class...> struct list_bases {};
template<TERNARY...> struct list_mixins {};
template<class ListBases, class ListMixins> struct install_impl;
template<class... Bs, TERNARY... Ms> struct install_impl<list_bases<Bs...>, list_mixins<Ms...>>
{
using base = inherit<Bs...>;
template<class B, class X, class Y> using mixin = typename compose_mixins<Ms...>::template mixin<B,X,Y>;
using result = apply_mixin<base, mixin, Bs...>;
};
template<class ListBases, class ListMixins> using install = typename install_impl<ListBases,ListMixins>::result;
using Einst = install< list_bases<A,B,C,D>, list_mixins<using_f,using_g,using_h> >;
using Etotal = apply_mixin<Einst, using_p, D>;
// далее мы можем наследоваться от сконструированных наследниковstruct Ffgh : Efgh {};
struct Fall : Eall {};
struct Ftotal : Etotal {};
// тестовая функцияtemplate<class E>
void run()
{
WHOAMI();
cout << endl;
E e;
e.f(1);
e.g('1');
e.h(1.f);
e.p();
cout << endl << endl;
}
int main()
{
run<Efgh>();
run<Eall>();
run<Etotal>();
run<Ffgh>();
run<Fall>();
run<Ftotal>();
}
Кстати, можете поужасаться метапрограммированию на новомодных template alias.
В смысле, офигел от увиденного, или компилятор айснулся?
Вообще, у меня есть воркераунд, — код, который не крешится даже на 2013.
Оба варианта писал в приступах вдохновения, независимо друг от друга, поэтому не так просто взять и ткнуть пальцем, что же там пошло не так.
Но вечером сяду за сравнение и, возможно, найду дистиллят.
BZ>>мне хватило прочесть соотв. разделы на https://isocpp.org/wiki/faq (до этого C++ я знал на уровне стандарта ~91-го года) C>Внезапно, C++ был стандартизован только в 1998 г. (ISO/IEC 14882:1998).
Там в качестве фактического стандарта была книжка Страуструп-Эллис.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!