Piszę szablon dla wyrażeń sparametryzowanych przez dowolną liczbę etykiet char
.Kompilator Intel C++ bardzo wolno kompiluje rekursywne zwracanie deklaracji.
Przy danej liście argumentów funkcja fabryczna zwraca wyrażenia różnych typów w zależności od tego, czy istnieją dwa argumenty tego samego typu, czy też są unikatowe.
Konkretny przykład: załóżmy, że A
jest obiektem "etykietowanym" z przeciążonym operator()
w celu utworzenia ?Expression<...>
. Niech a, b, ...
zostanie zadeklarowane jako etykiety LabelName<'a'>, LabelName<'b'>, ...
. Następnie A(a,b,c,d)
wytworzyłby UniqueExpression<'a','b','c','d'>
, podczas gdy A(a,c,b,c)
wygenerowałby zamiast tego RepeatedExpression<'a','c','b','c'>
.
Aby to osiągnąć, musiałem zdefiniować funkcję fabryczną ?Expression
z auto
i decltype
. Co więcej, decltype
musi się kaskadować do kolejnego decltype
, aż metaprogram zakończy rekursję przez argumenty i ostatecznie zdecyduje się na typ powrotu. Jako ilustrację, wyizolowałem dość minimalny kod dla metody fabrycznej.
template <typename... T> struct TypeList { };
template <char C> struct LabelName { };
template <typename... T> class UniqueExpression
{
// Contains implementation details in actual code
};
template <typename... T> class RepeatedExpression
{
// Contains implementation details in actual code
};
class ExpressionFactory {
private:
template <char _C, typename... T, typename... _T>
static UniqueExpression<T...>
_do_build(TypeList<T...>,
TypeList<LabelName<_C>>,
TypeList<>,
TypeList<_T...>)
{
return UniqueExpression<T...>();
}
template <char _C, typename... T, typename... _T1, typename... _T2, typename... _T3>
static RepeatedExpression<T...>
_do_build(TypeList<T...>,
TypeList<LabelName<_C>, _T1...>,
TypeList<LabelName<_C>, _T2...>,
TypeList<_T3...>)
{
return RepeatedExpression<T...>();
}
template <char _C1, char _C2, typename... T, typename... _T1, typename... _T2, typename... _T3>
static auto
_do_build(TypeList<T...>,
TypeList<LabelName<_C1>, _T1...>,
TypeList<LabelName<_C2>, _T2...>,
TypeList<_T3...>)
-> decltype(_do_build(TypeList<T...>(),
TypeList<LabelName<_C1>, _T1...>(),
TypeList<_T2...>(),
TypeList<_T3..., LabelName<_C2>>()))
{
return _do_build(TypeList<T...>(),
TypeList<LabelName<_C1>, _T1...>(),
TypeList<_T2...>(),
TypeList<_T3..., LabelName<_C2>>());
}
template <char _C1, char _C2, typename... T, typename... _T1, typename... _T2>
static auto
_do_build(TypeList<T...>,
TypeList<LabelName<_C1>, LabelName<_C2>, _T1...>,
TypeList<>,
TypeList<LabelName<_C2>, _T2...>)
-> decltype(_do_build(TypeList<T...>(),
TypeList<LabelName<_C2>, _T1...>(),
TypeList<_T2...>(),
TypeList<>()))
{
return _do_build(TypeList<T...>(),
TypeList<LabelName<_C2>, _T1...>(),
TypeList<_T2...>(),
TypeList<>());
}
public:
template <char C, typename... T>
static auto
build_expression(LabelName<C>, T...)
-> decltype(_do_build(TypeList<LabelName<C>, T...>(),
TypeList<LabelName<C>, T...>(),
TypeList<T...>(),
TypeList<>()))
{
return _do_build(TypeList<LabelName<C>, T...>(),
TypeList<LabelName<C>, T...>(),
TypeList<T...>(),
TypeList<>());
}
};
Fabryka można nazwać w programie tak: (w rzeczywistym programie istnieje inna klasa z przeciążonej operator()
który wzywa fabrykę)
int main()
{
LabelName<'a'> a;
LabelName<'b'> b;
...
LabelName<'j'> j;
auto expr = ExpressionFactory::build_expression(a,b,c,d,e,f,g,h,i,j);
// Perhaps do some cool stuff with expr
return 0;
}
Powyższy kod działa zgodnie z przeznaczeniem, i jest poprawnie skompilowany zarówno przez GCC, jak i kompilator Intel. Rozumiem, że kompilacja zajęłaby więcej czasu, aby wykonać rekurencyjne odliczanie szablonów, ponieważ zwiększam liczbę etykiet, których używam.
Na moim komputerze, jeśli build_expression
jest wywoływany z jednym argumentem, wówczas GCC 4.7.1 zajmuje średnio 0,26 sekundy. Czas kompilacji skaluje się do około 0,29 sekundy dla pięciu argumentów i do 0,62 sekundy dla dziesięciu argumentów. Wszystko to jest całkowicie uzasadnione.
Historia jest zupełnie inna w przypadku kompilatora Intel. ICPC 13.0.1 kompiluje kod jednokrotny w 0.35 sekundy, a czas kompilacji pozostaje prawie stały dla maksymalnie czterech argumentów. Przy pięciu argumentach czas kompilacji wzrasta do 12 sekund, a przy sześciu argumentach strzela ponad 9600 sekund (czyli ponad 2 godziny i 40 minut). Nie trzeba dodawać, że nie czekałem wystarczająco długo, aby dowiedzieć się, ile czasu zajmuje kompilacja wersji siedmiokonwersalnej.
dwa pytania od razu przychodzą na myśl:
Czy kompilator Intel szczególnie znane jako powolne kompilacji rekurencyjnej
decltype
?Czy istnieje sposób na przepisanie tego kodu w celu osiągnięcia tego samego efektu w sposób, który jest być może bardziej przyjazny kompilatorowi?
To jest na bok: http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-ac-ac-identifier nie używaj symboli zaczynających się od podkreślenia w Twój kod. Biblioteka standardowa działa, ale nie powinieneś. – Yakk
Czy jedyną rzeczą, o którą ci chodzi z do_build typu? Jeśli tak, spróbuj zwrócić 'struct SameType {template operator R() const {return R(); }}; '- jeśli to kompiluje, zmniejszyłoby to ilość wklejonej tablicy wklejania i może być wykładniczym przyspieszeniem w kompilacji. –
Yakk
Umieszczaj to pytanie na forach pomocy technicznej firmy Intel, w pobliżu jest wielu bardzo dobrze poinformowanych osób. –