Szablony wyrażeń i C++11
Spójrzmy na jedną szczególną zaletę szablonów wyrażeń: ETs może być używany do unikania tymczasowych wielkości wektorowych w pamięci, które występują w przeciążonych operatorach, takich jak:]}
template<typename T>
std::vector<T> operator+(const std::vector<T>& a, const std::vector<T>& b)
{
std::vector<T> tmp; // vector-sized temporary
for_each(...);
return tmp;
}
W C++11 Instrukcja return tej funkcji stosuje semantykę move. Brak kopii wektora. To zwycięstwo.
Jednakże, jeśli spojrzę na proste wyrażenie jak
d = a + b + c;
Widzę, że powyższa funkcja jest wywoływana dwa razy (dla obu operator+
), podczas gdy ostateczne przypisanie może być wykonane za pomocą move semantyka.
W sumie wykonywane są 2 pętle. To znaczy, że wydałem tymczasowe i przeczytałem je z powrotem zaraz po tym. Dla dużych wektorów wypada to z pamięci podręcznej. To gorsze niż szablony wyrażeń. Mogą zrobić wszystko w zaledwie 1 pętli. ETs może wykonać powyższy kod równoważny:
for(int i=0 ; i < vec_length ; ++i)
d[i] = a[i] + b[i] + c[i];
Zastanawiałem się, czy lambda razem z semantyką move, czy jakakolwiek inna nowa funkcja może zrobić tak dobrze jak ETs. Jakieś pomysły?
Edit:
Zasadniczo, używając technika ET kompilator buduje drzewo parse które przypomina wyrażenie algebraiczne z jego system typów. Drzewo to składa się z węzłów wewnętrznych i węzłów liściowych. Na węzły wewnętrzne reprezentują operacje (dodawanie, mnożenie itp.) i węzły liścia reprezentują odniesienia do obiektów danych.
Starałem się myśleć o całym procesie obliczeniowym w sposób maszyna stosu: weź operację ze stosu operacji i pociągnij kolejne argumenty ze stosu argumentów i ewaluacja operacja. Umieść wynik z powrotem na stosie czekając na operację.
Do reprezentowania tych dwóch różnych obiektów (stos operacji i dane
leaf stack) i bundled together a std::tuple
for the operations and a
std::tuple
dla danych pozostawia się w std::pair<>
. Początkowo I
użyłem std:vector
, ale spowodowało to nadmiarowość czasu pracy.
Cały proces przebiega w dwóch fazach: Inicjalizacja Maszyny stosu gdzie operacja i stos argumentów są inicjalizowane. Oraz etap oceny, który jest wywołane przez przypisanie sparowanych kontenerów do wektora.
I created a class Vec
which holds a private array<int,5>
(the
ładowności) i który posiada przeciążonego operatora przydziału, który
przyjmuje "ekspresję".
Globalny {[12] } jest przeciążony dla wszystkich kombinacji biorących
Vec
i "expression", aby umożliwić prawidłową obsługę również w przypadku
gdzie mamy więcej niż tylko a*b
. (Zauważ, że zamieniłem na to
przykład Wychowawczy do mnożenia-w zasadzie do szybko spot
imull
w asemblerze.)
To, co robi się najpierw przed rozpoczęciem oceny, to "wydobywanie"
wartości z zaangażowanych obiektów Vec
i inicjalizacja argumentu
stack. To było konieczne, aby nie mieć różnego rodzaju przedmiotów leżących
wokół: Wektory Indeksowalne i wyniki nieindeksowalne. To jest to, co
Extractor
jest dla. Jeszcze raz fajna sprawa: używane są różne szablony, które
w tym przypadku skutkuje brakiem nadmiarowości czasu pracy (wszystko to odbywa się w
Kompilacje czas).
Upshot. Nowe funkcje języka C++11 oferują różnorodne szablony, które (wraz z meta-programowaniem szablonów) otwierają obszar obliczeń czasu kompilacji. Pokazałem tutaj, jak korzyści z różne szablony może być używany do tworzenia kodu tak dobrego jak z tradycyjna technika ET.
#include<algorithm>
#include<iostream>
#include<vector>
#include<tuple>
#include<utility>
#include<array>
template<typename Target,typename Tuple, int N, bool end>
struct Extractor {
template < typename ... Args >
static Target index(int i,const Tuple& t, Args && ... args)
{
return Extractor<Target, Tuple, N+1,
std::tuple_size<Tuple>::value == N+1>::
index(i, t , std::forward<Args>(args)..., std::get<N>(t).vec[i] );
}
};
template < typename Target, typename Tuple, int N >
struct Extractor<Target,Tuple,N,true>
{
template < typename ... Args >
static Target index(int i,Tuple const& t,
Args && ... args) {
return Target(std::forward<Args>(args)...); }
};
template < typename ... Vs >
std::tuple<typename std::remove_reference<Vs>::type::type_t...>
extract(int i , const std::tuple<Vs...>& tpl)
{
return Extractor<std::tuple<typename std::remove_reference<Vs>::type::type_t...>,
std::tuple<Vs...>, 0,
std::tuple_size<std::tuple<Vs...> >::value == 0>::index(i,tpl);
}
struct Vec {
std::array<int,5> vec;
typedef int type_t;
template<typename... OPs,typename... VALs>
Vec& operator=(const std::pair< std::tuple<VALs...> , std::tuple<OPs...> >& e) {
for( int i = 0 ; i < vec.size() ; ++i ) {
vec[i] = eval( extract(i,e.first) , e.second );
}
}
};
template<int OpPos,int ValPos, bool end>
struct StackMachine {
template<typename... OPs,typename... VALs>
static void eval_pos( std::tuple<VALs...>& vals , const std::tuple<OPs...> & ops )
{
std::get<ValPos+1>( vals ) =
std::get<OpPos>(ops).apply( std::get<ValPos>( vals ) ,
std::get<ValPos+1>( vals ) );
StackMachine<OpPos+1,ValPos+1,sizeof...(OPs) == OpPos+1>::eval_pos(vals,ops);
}
};
template<int OpPos,int ValPos>
struct StackMachine<OpPos,ValPos,true> {
template<typename... OPs,typename... VALs>
static void eval_pos( std::tuple<VALs...>& vals ,
const std::tuple<OPs...> & ops )
{}
};
template<typename... OPs,typename... VALs>
int eval( const std::tuple<VALs...>& vals , const std::tuple<OPs...> & ops )
{
StackMachine<0,0,false>::eval_pos(const_cast<std::tuple<VALs...>&>(vals),ops);
return std::get<sizeof...(OPs)>(vals);
}
struct OpMul {
static int apply(const int& lhs,const int& rhs) {
return lhs*rhs;
}
};
std::pair< std::tuple< const Vec&, const Vec& > , std::tuple<OpMul> >
operator*(const Vec& lhs,const Vec& rhs)
{
return std::make_pair( std::tuple< const Vec&, const Vec& >( lhs , rhs ) ,
std::tuple<OpMul>( OpMul() ) );
}
template<typename... OPs,typename... VALs>
std::pair< std::tuple< const Vec&, VALs... > , std::tuple<OPs...,OpMul> >
operator*(const Vec& lhs,const std::pair< std::tuple< VALs... > , std::tuple<OPs...> >& rhs)
{
return std::make_pair( std::tuple_cat( rhs.first , std::tuple< const Vec& >(lhs) ) ,
std::tuple_cat( rhs.second , std::tuple<OpMul>( OpMul() ) ) );
}
template<typename... OPs,typename... VALs>
std::pair< std::tuple< const Vec&, VALs... > , std::tuple<OPs...,OpMul> >
operator*(const std::pair< std::tuple< VALs... > , std::tuple<OPs...> >& lhs,
const Vec& rhs)
{
return std::make_pair( std::tuple_cat( lhs.first , std::tuple< const Vec& >(rhs) ) ,
std::tuple_cat( lhs.second , std::tuple<OpMul>( OpMul() ) ) );
}
int main()
{
Vec d,c,b,a;
for( int i = 0 ; i < d.vec.size() ; ++i ) {
a.vec[i] = 10+i;
b.vec[i] = 20+i;
c.vec[i] = 30+i;
d.vec[i] = 0;
}
d = a * b * c * a;
for( int i = 0 ; i < d.vec.size() ; ++i )
std::cout << d.vec[i] << std::endl;
}
Asembler wygenerowany za pomocą g++-4.6 -O3
(musiałem wprowadzić pewną zależność runtime do inicjalizacji wektora, aby kompilator nie obliczył całości w czasie kompilacji i faktycznie widzisz instancje imull
.)
imull %esi, %edx
imull 32(%rsp), %edx
imull %edx, %esi
movl 68(%rsp), %edx
imull %ecx, %edx
movl %esi, (%rsp)
imull 36(%rsp), %edx
imull %ecx, %edx
movl 104(%rsp), %ecx
movl %edx, 4(%rsp)
movl 72(%rsp), %edx
imull %ecx, %edx
imull 40(%rsp), %edx
imull %ecx, %edx
movl 108(%rsp), %ecx
movl %edx, 8(%rsp)
movl 76(%rsp), %edx
imull %ecx, %edx
imull 44(%rsp), %edx
imull %ecx, %edx
movl 112(%rsp), %ecx
movl %edx, 12(%rsp)
movl 80(%rsp), %edx
imull %ecx, %edx
imull %eax, %edx
imull %ecx, %edx
movl %edx, 16(%rsp)
2 answers
Zastanawiałem się, czy lambda razem z semantyką move, czy jakakolwiek inna nowa funkcja może zrobić tak dobrze jak ETs. Jakieś pomysły?
Szybka Odpowiedź
Semantyka Move sama w sobie nie jest panaceum-techniki takie jak szablony wyrażeń (ETs) są nadal potrzebne w C++11, aby wyeliminować koszty ogólne, takie jak przenoszenie danych! Tak więc, aby szybko odpowiedzieć na twoje pytanie przed nurkowaniem do reszty mojej odpowiedzi, przenieść semantykę itp. nie do końca zastąp ETs, jak moja odpowiedź ilustruje poniżej.
Szczegółowa Odpowiedź
ETs zazwyczaj zwraca obiekty proxy, aby odroczyć ewaluację na później, więc nie ma natychmiastowej widocznej korzyści z funkcji języka C++11, dopóki kod nie uruchomi obliczeń. To powiedziawszy, nie chciaĹ ' oby siÄ ™ jednak pisaÄ ‡ kodu ET, ktĂłry uruchamia generowanie kodu w czasie wykonywania podczas budowania drzewa wyrazăłw z proxy. Ładnie, semantyka ruchu C++11 i doskonałe przekazywanie mogą pomóż uniknąć takich kosztów ogólnych, jeśli w przeciwnym razie wystąpi. (Nie byłoby to możliwe w C++03.)
Zasadniczo, pisząc ETs, chcemy wykorzystać cechy języka w taki sposób, aby wygenerować optymalny kod po wywołaniu funkcji Członkowskich zaangażowanych obiektów proxy. W C++11 będzie to m.in. używanie perfect forwarding, przenoszenie semantyki nad kopiowaniem, itp. jeśli taka jest rzeczywiście nadal potrzebna ponad to, co kompilator może już zrobić. Nazwa gry ma na celu zminimalizowanie generowany kod czasu pracy i / lub maksymalizuje szybkość czasu pracy i / lub minimalizuje obciążenie czasu pracy.
Chciałem wypróbować jakiś ETs z funkcjami C++11, aby zobaczyć, czy mogę ominąć wszystkie pośrednie tymczasowe instancje za pomocą wyrażenia a = b + c + d;
. (Ponieważ była to tylko zabawa przerwa od moich normalnych działań, więc nie porównuję go do lub napisać kod et wyłącznie za pomocą C++03. Nie martwiłem się również o wszystkie aspekty polerowania kodu, które pojawiają się poniżej.)
Na początek, nie użyj lambda - ponieważ wolałem używać jawnych typów i funkcji-więc nie będę argumentował za/przeciw lambda w odniesieniu do twojego pytania. Domyślam się, że byłyby one podobne do używania funktorów i nie byłyby lepsze od poniższego kodu non-ET (tzn. wymagane byłyby ruchy) - przynajmniej dopóki Kompilatory nie będą mogły automatycznie zoptymalizować lambda używając do tego własnego wewnętrznego ETs. Kod, który napisałem, jednak exploity poruszają semantykę i doskonałe przekazywanie. Oto co zrobiłem zaczynając od wyników i w końcu prezentujemy kod.
Stworzyłem klasę math_vector<N>
gdzie N==3
i definiuje wewnętrzną prywatną instancję std::array<long double, N>
. Elementy składowe są konstruktorem domyślnym, konstruktorami kopiowania i przenoszenia oraz przypisaniami, konstruktorem listy inicjalizacyjnej, destruktorem, składnikiem swap (), operatorem [], aby uzyskać dostęp do elementów wektora i operatorem+=. Używany bez szablonów wyrażeń, kod ten:
{
cout << "CASE 1:\n";
math_vector<3> a{1.0, 1.1, 1.2};
math_vector<3> b{2.0, 2.1, 2.2};
math_vector<3> c{3.0, 3.1, 3.2};
math_vector<3> d{4.0, 4.1, 4.2};
math_vector<3> result = a + b + c + d;
cout << '[' << &result << "]: " << result << "\n";
}
Wyjścia (przy kompilacji z clang++
3.1 lub g++
4.8 z -std=c++11 -O3
):
CASE 1:
0x7fff8d6edf50: math_vector(initlist)
0x7fff8d6edef0: math_vector(initlist)
0x7fff8d6ede90: math_vector(initlist)
0x7fff8d6ede30: math_vector(initlist)
0x7fff8d6edd70: math_vector(copy: 0x7fff8d6edf50)
0x7fff8d6edda0: math_vector(move: 0x7fff8d6edd70)
0x7fff8d6eddd0: math_vector(move: 0x7fff8d6edda0)
0x7fff8d6edda0: ~math_vector()
0x7fff8d6edd70: ~math_vector()
[0x7fff8d6eddd0]: (10,10.4,10.8)
0x7fff8d6eddd0: ~math_vector()
0x7fff8d6ede30: ~math_vector()
0x7fff8d6ede90: ~math_vector()
0x7fff8d6edef0: ~math_vector()
0x7fff8d6edf50: ~math_vector()
Tj. cztery jawnie skonstruowane instancje za pomocą list inicjujących (tj. pozycje initlist
), zmienna result
(tj. 0x7fff8d6eddd0
), a także sprawia, że dodatkowe trzy obiekty kopiują i przenoszą.
result
jako nazwaną zmienną.]}
{
cout << "CASE 2:\n";
math_vector<3> result =
math_vector<3>{1.0, 1.1, 1.2} +
math_vector<3>{2.0, 2.1, 2.2} +
math_vector<3>{3.0, 3.1, 3.2} +
math_vector<3>{4.0, 4.1, 4.2}
;
cout << '[' << &result << "]: " << result << "\n";
}
Który wyświetla to (ponownie, gdy ETs nie są używane):
CASE 2:
0x7fff8d6edcb0: math_vector(initlist)
0x7fff8d6edc50: math_vector(initlist)
0x7fff8d6edce0: math_vector(move: 0x7fff8d6edcb0)
0x7fff8d6edbf0: math_vector(initlist)
0x7fff8d6edd10: math_vector(move: 0x7fff8d6edce0)
0x7fff8d6edb90: math_vector(initlist)
0x7fff8d6edd40: math_vector(move: 0x7fff8d6edd10)
0x7fff8d6edb90: ~math_vector()
0x7fff8d6edd10: ~math_vector()
0x7fff8d6edbf0: ~math_vector()
0x7fff8d6edce0: ~math_vector()
0x7fff8d6edc50: ~math_vector()
0x7fff8d6edcb0: ~math_vector()
[0x7fff8d6edd40]: (10,10.4,10.8)
0x7fff8d6edd40: ~math_vector()
Co jest lepsze: tylko dodatkowy ruch tworzone są obiekty.
Ale chciałem czegoś lepszego: chciałem zero dodatkowych tymczasowych i mieć kod tak, jakbym go zakodował na twardo z jednym normalnym zastrzeżeniem kodowania: wszystkie jawnie utworzone typy byłyby nadal tworzone (tj. cztery konstruktory initlist
i result
). Aby to osiągnąć, dodałem kod szablonu wyrażenia w następujący sposób:
- Klasa proxy
math_vector_expr<LeftExpr,BinaryOp,RightExpr>
została stworzona do przechowywania wyrażenia jeszcze nie obliczonego, - Klasa proxy
plus_op
została stworzona do przechowywania dodawania operacja, - konstruktor został dodany do
math_vector
, aby zaakceptowaćmath_vector_expr
obiekt, oraz - dodano funkcje" starter", aby wywołać tworzenie szablonu wyrażenia.
Wyniki korzystania z ETs są wspaniałe: żadnych dodatkowych tymczasowych w obu przypadkach! Dwa poprzednie przypadki powyżej teraz wyjście:
CASE 1:
0x7fffe7180c60: math_vector(initlist)
0x7fffe7180c90: math_vector(initlist)
0x7fffe7180cc0: math_vector(initlist)
0x7fffe7180cf0: math_vector(initlist)
0x7fffe7180d20: math_vector(expr: 0x7fffe7180d90)
[0x7fffe7180d20]: (10,10.4,10.8)
0x7fffe7180d20: ~math_vector()
0x7fffe7180cf0: ~math_vector()
0x7fffe7180cc0: ~math_vector()
0x7fffe7180c90: ~math_vector()
0x7fffe7180c60: ~math_vector()
CASE 2:
0x7fffe7180dd0: math_vector(initlist)
0x7fffe7180e20: math_vector(initlist)
0x7fffe7180e70: math_vector(initlist)
0x7fffe7180eb0: math_vector(initlist)
0x7fffe7180d20: math_vector(expr: 0x7fffe7180dc0)
0x7fffe7180eb0: ~math_vector()
0x7fffe7180e70: ~math_vector()
0x7fffe7180e20: ~math_vector()
0x7fffe7180dd0: ~math_vector()
[0x7fffe7180d20]: (10,10.4,10.8)
0x7fffe7180d20: ~math_vector()
Czyli dokładnie 5 wywołań konstruktora i 5 wywołań destruktora w każdym przypadku. W rzeczywistości, jeśli poprosisz kompilator o wygenerowanie kodu asemblera pomiędzy 4 initlist
wywołania konstruktora i wyjście result
otrzymuje się piękny ciąg kodu asemblera:
fldt 128(%rsp)
leaq 128(%rsp), %rdi
leaq 80(%rsp), %rbp
fldt 176(%rsp)
faddp %st, %st(1)
fldt 224(%rsp)
faddp %st, %st(1)
fldt 272(%rsp)
faddp %st, %st(1)
fstpt 80(%rsp)
fldt 144(%rsp)
fldt 192(%rsp)
faddp %st, %st(1)
fldt 240(%rsp)
faddp %st, %st(1)
fldt 288(%rsp)
faddp %st, %st(1)
fstpt 96(%rsp)
fldt 160(%rsp)
fldt 208(%rsp)
faddp %st, %st(1)
fldt 256(%rsp)
faddp %st, %st(1)
fldt 304(%rsp)
faddp %st, %st(1)
fstpt 112(%rsp)
Z g++
i clang++
wyprowadza podobny (nawet mniejszy) kod. Brak wywołań funkcji itp. -- tylko kilka dodatków, które jest dokładnie to, co chcesz!
Kod C++11, aby to osiągnąć, znajduje się poniżej. Po prostu #define DONT_USE_EXPR_TEMPL
nie używać ETs lub nie definiować go w ogóle używać ETs.
#include <array>
#include <algorithm>
#include <initializer_list>
#include <type_traits>
#include <iostream>
//#define DONT_USE_EXPR_TEMPL
//===========================================================================
template <std::size_t N> class math_vector;
template <
typename LeftExpr,
typename BinaryOp,
typename RightExpr
>
class math_vector_expr
{
public:
math_vector_expr() = delete;
math_vector_expr(LeftExpr l, RightExpr r) :
l_(std::forward<LeftExpr>(l)),
r_(std::forward<RightExpr>(r))
{
}
// Prohibit copying...
math_vector_expr(math_vector_expr const&) = delete;
math_vector_expr& operator =(math_vector_expr const&) = delete;
// Allow moves...
math_vector_expr(math_vector_expr&&) = default;
math_vector_expr& operator =(math_vector_expr&&) = default;
template <typename RE>
auto operator +(RE&& re) const ->
math_vector_expr<
math_vector_expr<LeftExpr,BinaryOp,RightExpr> const&,
BinaryOp,
decltype(std::forward<RE>(re))
>
{
return
math_vector_expr<
math_vector_expr<LeftExpr,BinaryOp,RightExpr> const&,
BinaryOp,
decltype(std::forward<RE>(re))
>(*this, std::forward<RE>(re))
;
}
auto le() ->
typename std::add_lvalue_reference<LeftExpr>::type
{ return l_; }
auto le() const ->
typename std::add_lvalue_reference<
typename std::add_const<LeftExpr>::type
>::type
{ return l_; }
auto re() ->
typename std::add_lvalue_reference<RightExpr>::type
{ return r_; }
auto re() const ->
typename std::add_lvalue_reference<
typename std::add_const<RightExpr>::type
>::type
{ return r_; }
auto operator [](std::size_t index) const ->
decltype(
BinaryOp::apply(this->le()[index], this->re()[index])
)
{
return BinaryOp::apply(le()[index], re()[index]);
}
private:
LeftExpr l_;
RightExpr r_;
};
//===========================================================================
template <typename T>
struct plus_op
{
static T apply(T const& a, T const& b)
{
return a + b;
}
static T apply(T&& a, T const& b)
{
a += b;
return std::move(a);
}
static T apply(T const& a, T&& b)
{
b += a;
return std::move(b);
}
static T apply(T&& a, T&& b)
{
a += b;
return std::move(a);
}
};
//===========================================================================
template <std::size_t N>
class math_vector
{
using impl_type = std::array<long double, N>;
public:
math_vector()
{
using namespace std;
fill(begin(v_), end(v_), impl_type{});
std::cout << this << ": math_vector()" << endl;
}
math_vector(math_vector const& mv) noexcept
{
using namespace std;
copy(begin(mv.v_), end(mv.v_), begin(v_));
std::cout << this << ": math_vector(copy: " << &mv << ")" << endl;
}
math_vector(math_vector&& mv) noexcept
{
using namespace std;
move(begin(mv.v_), end(mv.v_), begin(v_));
std::cout << this << ": math_vector(move: " << &mv << ")" << endl;
}
math_vector(std::initializer_list<typename impl_type::value_type> l)
{
using namespace std;
copy(begin(l), end(l), begin(v_));
std::cout << this << ": math_vector(initlist)" << endl;
}
math_vector& operator =(math_vector const& mv) noexcept
{
using namespace std;
copy(begin(mv.v_), end(mv.v_), begin(v_));
std::cout << this << ": math_vector op =(copy: " << &mv << ")" << endl;
return *this;
}
math_vector& operator =(math_vector&& mv) noexcept
{
using namespace std;
move(begin(mv.v_), end(mv.v_), begin(v_));
std::cout << this << ": math_vector op =(move: " << &mv << ")" << endl;
return *this;
}
~math_vector()
{
using namespace std;
std::cout << this << ": ~math_vector()" << endl;
}
void swap(math_vector& mv)
{
using namespace std;
for (std::size_t i = 0; i<N; ++i)
swap(v_[i], mv[i]);
}
auto operator [](std::size_t index) const
-> typename impl_type::value_type const&
{
return v_[index];
}
auto operator [](std::size_t index)
-> typename impl_type::value_type&
{
return v_[index];
}
math_vector& operator +=(math_vector const& b)
{
for (std::size_t i = 0; i<N; ++i)
v_[i] += b[i];
return *this;
}
#ifndef DONT_USE_EXPR_TEMPL
template <typename LE, typename Op, typename RE>
math_vector(math_vector_expr<LE,Op,RE>&& mve)
{
for (std::size_t i = 0; i < N; ++i)
v_[i] = mve[i];
std::cout << this << ": math_vector(expr: " << &mve << ")" << std::endl;
}
template <typename RightExpr>
math_vector& operator =(RightExpr&& re)
{
for (std::size_t i = 0; i<N; ++i)
v_[i] = re[i];
return *this;
}
template <typename RightExpr>
math_vector& operator +=(RightExpr&& re)
{
for (std::size_t i = 0; i<N; ++i)
v_[i] += re[i];
return *this;
}
template <typename RightExpr>
auto operator +(RightExpr&& re) const ->
math_vector_expr<
math_vector const&,
plus_op<typename impl_type::value_type>,
decltype(std::forward<RightExpr>(re))
>
{
return
math_vector_expr<
math_vector const&,
plus_op<typename impl_type::value_type>,
decltype(std::forward<RightExpr>(re))
>(
*this,
std::forward<RightExpr>(re)
)
;
}
#endif // #ifndef DONT_USE_EXPR_TEMPL
private:
impl_type v_;
};
//===========================================================================
template <std::size_t N>
inline void swap(math_vector<N>& a, math_vector<N>& b)
{
a.swap(b);
}
//===========================================================================
#ifdef DONT_USE_EXPR_TEMPL
template <std::size_t N>
inline math_vector<N> operator +(
math_vector<N> const& a,
math_vector<N> const& b
)
{
math_vector<N> retval(a);
retval += b;
return retval;
}
template <std::size_t N>
inline math_vector<N> operator +(
math_vector<N>&& a,
math_vector<N> const& b
)
{
a += b;
return std::move(a);
}
template <std::size_t N>
inline math_vector<N> operator +(
math_vector<N> const& a,
math_vector<N>&& b
)
{
b += a;
return std::move(b);
}
template <std::size_t N>
inline math_vector<N> operator +(
math_vector<N>&& a,
math_vector<N>&& b
)
{
a += std::move(b);
return std::move(a);
}
#endif // #ifdef DONT_USE_EXPR_TEMPL
//===========================================================================
template <std::size_t N>
std::ostream& operator <<(std::ostream& os, math_vector<N> const& mv)
{
os << '(';
for (std::size_t i = 0; i < N; ++i)
os << mv[i] << ((i+1 != N) ? ',' : ')');
return os;
}
//===========================================================================
int main()
{
using namespace std;
try
{
{
cout << "CASE 1:\n";
math_vector<3> a{1.0, 1.1, 1.2};
math_vector<3> b{2.0, 2.1, 2.2};
math_vector<3> c{3.0, 3.1, 3.2};
math_vector<3> d{4.0, 4.1, 4.2};
math_vector<3> result = a + b + c + d;
cout << '[' << &result << "]: " << result << "\n";
}
cout << endl;
{
cout << "CASE 2:\n";
math_vector<3> result =
math_vector<3>{1.0, 1.1, 1.2} +
math_vector<3>{2.0, 2.1, 2.2} +
math_vector<3>{3.0, 3.1, 3.2} +
math_vector<3>{4.0, 4.1, 4.2}
;
cout << '[' << &result << "]: " << result << "\n";
}
}
catch (...)
{
return 1;
}
}
//===========================================================================
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-04 22:44:28
Oto poprawiona wersja kodu Paula Preney ' a. Poinformowałem autora mailem i w komentarzach; napisałem edycję, ale została odrzucona przez niewykwalifikowanych recenzentów.
Błąd w oryginalnym kodzie jest taki, że parametr szablonu binaryop math_vector_expr::operator+ jest naprawiony.
#include <array>
#include <algorithm>
#include <initializer_list>
#include <type_traits>
#include <iostream>
//#define DONT_USE_EXPR_TEMPL
//===========================================================================
template <std::size_t N> class math_vector;
template <typename T> struct plus_op;
template <
typename LeftExpr,
typename BinaryOp,
typename RightExpr
>
class math_vector_expr
{
public:
typedef typename std::remove_reference<LeftExpr>::type::value_type value_type;
math_vector_expr() = delete;
math_vector_expr(LeftExpr l, RightExpr r) :
l_(std::forward<LeftExpr>(l)),
r_(std::forward<RightExpr>(r))
{
}
// Prohibit copying...
math_vector_expr(math_vector_expr const&) = delete;
math_vector_expr& operator =(math_vector_expr const&) = delete;
// Allow moves...
math_vector_expr(math_vector_expr&&) = default;
math_vector_expr& operator =(math_vector_expr&&) = default;
template <typename RE>
auto operator +(RE&& re) const ->
math_vector_expr<
math_vector_expr<LeftExpr,BinaryOp,RightExpr> const&,
plus_op<value_type>,
decltype(std::forward<RE>(re))
>
{
return
math_vector_expr<
math_vector_expr<LeftExpr,BinaryOp,RightExpr> const&,
plus_op<value_type>,
decltype(std::forward<RE>(re))
>(*this, std::forward<RE>(re))
;
}
auto le() ->
typename std::add_lvalue_reference<LeftExpr>::type
{ return l_; }
auto le() const ->
typename std::add_lvalue_reference<
typename std::add_const<LeftExpr>::type
>::type
{ return l_; }
auto re() ->
typename std::add_lvalue_reference<RightExpr>::type
{ return r_; }
auto re() const ->
typename std::add_lvalue_reference<
typename std::add_const<RightExpr>::type
>::type
{ return r_; }
auto operator [](std::size_t index) const ->
value_type
{
return BinaryOp::apply(le()[index], re()[index]);
}
private:
LeftExpr l_;
RightExpr r_;
};
//===========================================================================
template <typename T>
struct plus_op
{
static T apply(T const& a, T const& b)
{
return a + b;
}
static T apply(T&& a, T const& b)
{
a += b;
return std::move(a);
}
static T apply(T const& a, T&& b)
{
b += a;
return std::move(b);
}
static T apply(T&& a, T&& b)
{
a += b;
return std::move(a);
}
};
//===========================================================================
template <std::size_t N>
class math_vector
{
using impl_type = std::array<long double, N>;
public:
typedef typename impl_type::value_type value_type;
math_vector()
{
using namespace std;
fill(begin(v_), end(v_), impl_type{});
std::cout << this << ": math_vector()" << endl;
}
math_vector(math_vector const& mv) noexcept
{
using namespace std;
copy(begin(mv.v_), end(mv.v_), begin(v_));
std::cout << this << ": math_vector(copy: " << &mv << ")" << endl;
}
math_vector(math_vector&& mv) noexcept
{
using namespace std;
move(begin(mv.v_), end(mv.v_), begin(v_));
std::cout << this << ": math_vector(move: " << &mv << ")" << endl;
}
math_vector(std::initializer_list<value_type> l)
{
using namespace std;
copy(begin(l), end(l), begin(v_));
std::cout << this << ": math_vector(initlist)" << endl;
}
math_vector& operator =(math_vector const& mv) noexcept
{
using namespace std;
copy(begin(mv.v_), end(mv.v_), begin(v_));
std::cout << this << ": math_vector op =(copy: " << &mv << ")" << endl;
return *this;
}
math_vector& operator =(math_vector&& mv) noexcept
{
using namespace std;
move(begin(mv.v_), end(mv.v_), begin(v_));
std::cout << this << ": math_vector op =(move: " << &mv << ")" << endl;
return *this;
}
~math_vector()
{
using namespace std;
std::cout << this << ": ~math_vector()" << endl;
}
void swap(math_vector& mv)
{
using namespace std;
for (std::size_t i = 0; i<N; ++i)
swap(v_[i], mv[i]);
}
auto operator [](std::size_t index) const
-> value_type const&
{
return v_[index];
}
auto operator [](std::size_t index)
-> value_type&
{
return v_[index];
}
math_vector& operator +=(math_vector const& b)
{
for (std::size_t i = 0; i<N; ++i)
v_[i] += b[i];
return *this;
}
#ifndef DONT_USE_EXPR_TEMPL
template <typename LE, typename Op, typename RE>
math_vector(math_vector_expr<LE,Op,RE>&& mve)
{
for (std::size_t i = 0; i < N; ++i)
v_[i] = mve[i];
std::cout << this << ": math_vector(expr: " << &mve << ")" << std::endl;
}
template <typename RightExpr>
math_vector& operator =(RightExpr&& re)
{
for (std::size_t i = 0; i<N; ++i)
v_[i] = re[i];
return *this;
}
template <typename RightExpr>
math_vector& operator +=(RightExpr&& re)
{
for (std::size_t i = 0; i<N; ++i)
v_[i] += re[i];
return *this;
}
template <typename RightExpr>
auto operator +(RightExpr&& re) const ->
math_vector_expr<
math_vector const&,
plus_op<value_type>,
decltype(std::forward<RightExpr>(re))
>
{
return
math_vector_expr<
math_vector const&,
plus_op<value_type>,
decltype(std::forward<RightExpr>(re))
>(
*this,
std::forward<RightExpr>(re)
)
;
}
#endif // #ifndef DONT_USE_EXPR_TEMPL
private:
impl_type v_;
};
//===========================================================================
template <std::size_t N>
inline void swap(math_vector<N>& a, math_vector<N>& b)
{
a.swap(b);
}
//===========================================================================
#ifdef DONT_USE_EXPR_TEMPL
template <std::size_t N>
inline math_vector<N> operator +(
math_vector<N> const& a,
math_vector<N> const& b
)
{
math_vector<N> retval(a);
retval += b;
return retval;
}
template <std::size_t N>
inline math_vector<N> operator +(
math_vector<N>&& a,
math_vector<N> const& b
)
{
a += b;
return std::move(a);
}
template <std::size_t N>
inline math_vector<N> operator +(
math_vector<N> const& a,
math_vector<N>&& b
)
{
b += a;
return std::move(b);
}
template <std::size_t N>
inline math_vector<N> operator +(
math_vector<N>&& a,
math_vector<N>&& b
)
{
a += std::move(b);
return std::move(a);
}
#endif // #ifdef DONT_USE_EXPR_TEMPL
//===========================================================================
template <std::size_t N>
std::ostream& operator <<(std::ostream& os, math_vector<N> const& mv)
{
os << '(';
for (std::size_t i = 0; i < N; ++i)
os << mv[i] << ((i+1 != N) ? ',' : ')');
return os;
}
//===========================================================================
int main()
{
using namespace std;
try
{
{
cout << "CASE 1:\n";
math_vector<3> a{1.0, 1.1, 1.2};
math_vector<3> b{2.0, 2.1, 2.2};
math_vector<3> c{3.0, 3.1, 3.2};
math_vector<3> d{4.0, 4.1, 4.2};
math_vector<3> result = a + b + c + d;
cout << '[' << &result << "]: " << result << "\n";
}
cout << endl;
{
cout << "CASE 2:\n";
math_vector<3> result =
math_vector<3>{1.0, 1.1, 1.2} +
math_vector<3>{2.0, 2.1, 2.2} +
math_vector<3>{3.0, 3.1, 3.2} +
math_vector<3>{4.0, 4.1, 4.2}
;
cout << '[' << &result << "]: " << result << "\n";
}
}
catch (...)
{
return 1;
}
}
//===========================================================================
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-18 10:13:14