Emulating 'swizzling' in C++

A

Alan Woodland

Hi,

A number of 'C-style' languages define a 'swizzle' operator which allows
the permutation of the elements of vector datatypes. This is typically
done with an extra meaning to the . operator.

In C++ we can't overload operator. for fairly good reasons! Is there a
tidier way of emulating this syntax than the following:

#include <iostream>
#define zyxw swizzle(3,2,1,0)
struct ivec4 {
int data[4];
ivec4 swizzle(unsigned char p1, unsigned char p2, unsigned char
p3, unsigned char p4) {
ivec4 ret;
ret.data[0] = data[p1];
ret.data[1] = data[p2];
ret.data[2] = data[p3];
ret.data[3] = data[p4];
return ret;
}
};

int main() {
ivec4 f1, f2;
f1.data[0] = -666;

f2 = f1.zyxw;
std::cout << f2.data[3] << std::endl;
return 0;
}

This has some pretty serious problems, because of the macro use (and
generating a macro like this for all possible permutations of larger
vectors is quite sizeable!)

Can anyone suggest a cleaner way to achieve this 'syntax' within the
existing (including proposed C++0x) language?

Thanks,
Alan
 
K

Kenneth 'Bessarion' Boyd

Hi,

A number of 'C-style' languages define a 'swizzle' operator which allows
the permutation of the elements of vector datatypes. This is typically
done with an extra meaning to the . operator.

In C++ we can't overload operator. for fairly good reasons! Is there a
tidier way of emulating this syntax than the following:

#include <iostream>
#define zyxw swizzle(3,2,1,0)
struct ivec4 {
        int data[4];
        ivec4 swizzle(unsigned char p1, unsigned char p2, unsigned char
p3, unsigned char p4) {
                ivec4 ret;
                ret.data[0] = data[p1];
                ret.data[1] = data[p2];
                ret.data[2] = data[p3];
                ret.data[3] = data[p4];
                return ret;
        }

};

int main() {
        ivec4 f1, f2;
        f1.data[0] = -666;

        f2 = f1.zyxw;
        std::cout << f2.data[3] << std::endl;
        return 0;

}

This has some pretty serious problems, because of the macro use (and
generating a macro like this for all possible permutations of larger
vectors is quite sizeable!)

Yes, the suggested global macro is merely a code obfuscation device.
A more conventional define would be

#define ZYXW swizzle(3,2,1,0)

which at least uses the usual convention for making macros obvious
where they are used.

The proposed explicit sabotage of maintainability can be made
acceptably minor by:
* first stabilizing the code,
* *then* #define'ing the macro shortly before it is needed, and
* #undef'ing the macro shortly after its last use.

This keeps the macro from forcing contortions elsewhere to avoid it.
Can anyone suggest a cleaner way to achieve this 'syntax' within the
existing (including proposed C++0x) language?

My imagination is lacking. Your proposed swizzle member function is
close to optimal syntax for self-permutation.

As Pete Becker observed, the optimal general syntax is close to
std::tr1::tuple and auxilliaries, which can be reimplemented wherever
necessary.
 
F

Francesco S. Carta

Hi,

A number of 'C-style' languages define a 'swizzle' operator which allows
the permutation of the elements of vector datatypes. This is typically
done with an extra meaning to the . operator.

In C++ we can't overload operator. for fairly good reasons! Is there a
tidier way of emulating this syntax than the following:

#include <iostream>
#define zyxw swizzle(3,2,1,0)
struct ivec4 {
int data[4];
ivec4 swizzle(unsigned char p1, unsigned char p2, unsigned char
p3, unsigned char p4) {
ivec4 ret;
ret.data[0] = data[p1];
ret.data[1] = data[p2];
ret.data[2] = data[p3];
ret.data[3] = data[p4];
return ret;
}

};

int main() {
ivec4 f1, f2;
f1.data[0] = -666;

f2 = f1.zyxw;
std::cout << f2.data[3] << std::endl;
return 0;

}

This has some pretty serious problems, because of the macro use (and
generating a macro like this for all possible permutations of larger
vectors is quite sizeable!)

Can anyone suggest a cleaner way to achieve this 'syntax' within the
existing (including proposed C++0x) language?

I suppose you want be able to call not only .zyxw, but also something
like .wwyy, onto those objects, seems like the GC meaning of
"swizzle". I don't catch the reason why you speak about "larger
arrays"... are you going to need something like .xasyauacbwk?

I don't know.

In any case, if you can live with the following solution - which
obliges you to handle exceptions and obliges you to use the '("wxyz")'
notation - you might eventually extend it to your needs. At least it
saves you from writing all the permutations by hand.

-------
#include <iostream>
#include <string>

using namespace std;

struct ivec4 {
int data[4];
struct mismatch {};
ivec4(int i0 = 0, int i1 = 0, int i2 = 0, int i3 = 0) {
data[0] = i0;
data[1] = i1;
data[2] = i2;
data[3] = i3;
}
ivec4 operator()(const string& order) const throw(mismatch) {
if (order.size() != 4) throw mismatch();
ivec4 result;
for (int i = 0; i < 4; ++i) {
switch (order) {
case 'w':
result.data = data[0];
break;
case 'x':
result.data = data[1];
break;
case 'y':
result.data = data[2];
break;
case 'z':
result.data = data[3];
break;
default:
throw mismatch();
}
}
return result;
}
};

ostream& operator<<(ostream& os, const ivec4& v) {
os << "{" << v.data[0];
os << ", " << v.data[1];
os << ", " << v.data[2];
os << ", " << v.data[3] << "}";
return os;
}

int main()
{
ivec4 vec1(10, 20, 30, 40);
ivec4 vec2;
vec2 = vec1("wwxx");
cout << vec1 << endl;
cout << vec2 << endl;
}
 
F

Francesco S. Carta

Hi,

A number of 'C-style' languages define a 'swizzle' operator which allows
the permutation of the elements of vector datatypes. This is typically
done with an extra meaning to the . operator.

In C++ we can't overload operator. for fairly good reasons! Is there a
tidier way of emulating this syntax than the following:

#include <iostream>
#define zyxw swizzle(3,2,1,0)
struct ivec4 {
        int data[4];
        ivec4 swizzle(unsigned char p1, unsigned char p2, unsigned char
p3, unsigned char p4) {
                ivec4 ret;
                ret.data[0] = data[p1];
                ret.data[1] = data[p2];
                ret.data[2] = data[p3];
                ret.data[3] = data[p4];
                return ret;
        }

};

int main() {
        ivec4 f1, f2;
        f1.data[0] = -666;

        f2 = f1.zyxw;
        std::cout << f2.data[3] << std::endl;
        return 0;

}

This has some pretty serious problems, because of the macro use (and
generating a macro like this for all possible permutations of larger
vectors is quite sizeable!)

Can anyone suggest a cleaner way to achieve this 'syntax' within the
existing (including proposed C++0x) language?

See if you like this ugly beast too:

-------
#include <iostream>
#include <string>
#include <sstream>
#include <vector>

using namespace std;

template<bool> struct CompileTimeAssert;
template<> struct CompileTimeAssert<true> {};
#define STATIC_ASSERT(expr) CompileTimeAssert<(expr)>()

string tostring(int i) {
ostringstream oss;
oss << i;
return oss.str();
}

struct vec_error {
const string msg;
vec_error(const string& s) : msg(s) {}
};

template<class T>
class vec_base {
public:
virtual size_t size() const = 0;
virtual T& operator[](int) throw(vec_error) = 0;
virtual T operator[](int) const throw(vec_error) = 0;
virtual ~vec_base() {};
};

template<char first_char, char last_char, class T>
class vec : public vec_base<T> {
vector<T> v_data;
static const int v_size = last_char - first_char + 1;
T& checked(int vi) {
if (0 <= vi && vi < v_size) {
return v_data[vi];
} else {
throw vec_error(string("index out of range: ") + tostring(vi));
}
}
const T& checked(int vi) const {
if (0 <= vi && vi < v_size) {
return v_data[vi];
} else {
throw vec_error(string("index out of range: ") + tostring(vi));
}
}
public:
vec() {
STATIC_ASSERT(v_size > 0);
v_data.resize(v_size);
}
size_t size() const {
return v_size;
}
vec operator()(const string& order) const throw(vec_error) {
if (order.size() != v_size) throw vec_error("string size error");
vec result;
for (int i = 0; i < v_size; ++i) {
try {
result.v_data = checked(order - first_char);
} catch (vec_error) {
throw vec_error(string("char out of range: ") + order);
}
}
return result;
}
T& operator()(char ch) throw(vec_error) {
try {
return checked(ch - first_char);
} catch (vec_error) {
throw vec_error(string("char out of range: ") + ch);
}
}
T operator()(char ch) const throw(vec_error) {
try {
return checked(ch - first_char);
} catch (vec_error) {
throw vec_error(string("char out of range: ") + ch);
}
}
T& operator[](int vi) throw(vec_error) {
return checked(vi);
}
T operator[](int vi) const throw(vec_error) {
return checked(vi);
}
void detail_to(ostream& os) const {
char ch = first_char;
os << "{" << endl;
for (int i = 0; i < v_size-1; ++i) {
os << " '" << ch++ << "' == " << v_data << endl;
}
os << " '" << ch << "' == " << v_data[v_size-1] << endl;
os << "}" << endl;
}
};

template<class T>
ostream& operator<<(ostream& os, const vec_base<T>& v) {
os << "{";
for (int i = 0, e = v.size()-1; i < e; ++i) {
os << v << ", ";
}
os << v[v.size()-1] << "}";
return os;
}

void test() {
vec<'x', 'z', string> xz1, xz2;

xz1('x') = "roll";
xz1('y') = "pitch";
xz1('z') = "yaw";

xz2 = xz1("zxy");

cout << "xz1 == " << xz1 << endl;
cout << "xz2 == " << xz2 << endl;

vec<'a', 'f', int> af1, af2;

for (int i = 0, e = af1.size(); i < e; ++i) {
af1 = (i+1) * 10;
}

af2 = af1("fedcba");

cout << "af1 details:" << endl;
af1.detail_to(cout);
cout << "af2 details:" << endl;
af2.detail_to(cout);

}

int main() {
try {
test();
} catch (vec_error m) {
cout << "vec_error! " << m.msg << endl;
}
}

-------

Output:

-------
xz1 == {roll, pitch, yaw}
xz2 == {yaw, roll, pitch}
af1 details:
{
'a' == 10
'b' == 20
'c' == 30
'd' == 40
'e' == 50
'f' == 60
}
af2 details:
{
'a' == 60
'b' == 50
'c' == 40
'd' == 30
'e' == 20
'f' == 10
}
 
K

Kenneth 'Bessarion' Boyd

Hi,

A number of 'C-style' languages define a 'swizzle' operator which allows
the permutation of the elements of vector datatypes. This is typically
done with an extra meaning to the . operator.

Please accept my apologies for tactlessness.
In C++ we can't overload operator. for fairly good reasons! Is there a
tidier way of emulating this syntax than the following:

#include <iostream>
#define zyxw swizzle(3,2,1,0)
struct ivec4 {
        int data[4];
        ivec4 swizzle(unsigned char p1, unsigned char p2, unsigned char
p3, unsigned char p4) {
                ivec4 ret;
                ret.data[0] = data[p1];
                ret.data[1] = data[p2];
                ret.data[2] = data[p3];
                ret.data[3] = data[p4];
                return ret;
        }

Minimally. Replace the #define with a member function

ivec4 zyxw() {return swizzle(3,2,1,0);}

then the proposed main becomes:

int main() {
ivec4 f1, f2;
f1.data[0] = -666;

f2 = f1.zyxw();
std::cout << f2.data[3] << std::endl;
return 0;
}

Ignoring problems with copying undefined values, this should be fairly
close to the intended syntax. Not all development will implement this
cleanly. (I'm not sure how many will strip uncalled functions from an
executable.)
This has some pretty serious problems, because of the macro use (and
generating a macro like this for all possible permutations of larger
vectors is quite sizeable!)

The inline function approach still requires generating all possible
inline functions.
 
J

Jorgen Grahn

Hi,

A number of 'C-style' languages define a 'swizzle' operator which allows
the permutation of the elements of vector datatypes. This is typically
done with an extra meaning to the . operator.

Which languages are that? Not even Perl has a operator called
'swizzle' (unless it's new in Perl 6).

/Jorgen
 
R

Richard

[Please do not mail me a copy of your followup]

Jorgen Grahn <[email protected]> spake the secret code
Which languages are that? Not even Perl has a operator called
'swizzle' (unless it's new in Perl 6).

It comes from GPU shader languages where the registers are 4-component
vectors and variables declared as vectors can arbitrarily multiplex
(and replicate) the components of the vectors to product new vectors.
This happens quickly and directly in the hardware and so is supported
as an intrinsic on the vector types.

Look up Cg, GLSL and HLSL on Wikipedia for more info.
 
M

Michael Doubez

Hi,

A number of 'C-style' languages define a 'swizzle' operator which allows
the permutation of the elements of vector datatypes. This is typically
done with an extra meaning to the . operator.

In C++ we can't overload operator. for fairly good reasons! Is there a
tidier way of emulating this syntax than the following:

#include <iostream>
#define zyxw swizzle(3,2,1,0)
struct ivec4 {
        int data[4];
        ivec4 swizzle(unsigned char p1, unsigned char p2, unsigned char
p3, unsigned char p4) {
                ivec4 ret;
                ret.data[0] = data[p1];
                ret.data[1] = data[p2];
                ret.data[2] = data[p3];
                ret.data[3] = data[p4];
                return ret;
        }

};

int main() {
        ivec4 f1, f2;
        f1.data[0] = -666;

        f2 = f1.zyxw;
        std::cout << f2.data[3] << std::endl;
        return 0;

}

This has some pretty serious problems, because of the macro use (and
generating a macro like this for all possible permutations of larger
vectors is quite sizeable!)

Can anyone suggest a cleaner way to achieve this 'syntax' within the
existing (including proposed C++0x) language?

Well, I don't know what you mean by cleaner but I can get something
working with an approaching syntax with low runtime overhead.

I'll try to have something like the following working:
int main() {
ivec4 f1, f2;
f1.data[0] = -666;

f2 = f1.swizzle.z.y.x.w;
std::cout << f2.data[2] << std::endl; // expect -666
return 0;
}

Here comes the show.

In order to achieve that, I will define a temple operator=() that can
take a type representing the information data+swizzle.

The information content will be the data table (f1.data) and the
swizzle will be represented as a type:

// type representing the swizzle transformation
// index are mapped in parameter
template<int p0, int p1, int p2, int p3>
struct swizzle_spec
{
static const int index0 = p0;
static const int index1 = p1;
static const int index2 = p2;
static const int index3 = p3;
};

Hence the operator:
template<class TSwizzleContainer>
ivec4& operator=(const TSwizzleContainer& rhs)
{
typedef typename TSwizzleContainer::type TSwizzle;
data[0] = rhs.content[TSwizzle::index0];
data[1] = rhs.content[TSwizzle::index1];
data[2] = rhs.content[TSwizzle::index2];
data[3] = rhs.content[TSwizzle::index3];
return *this;
}

Now we must determine a suitable type for this SwizzleContainer that
will effectively store the sequence x.y.z.w or any combination with
the original pointer, without duplication. (Note if ivec4 is a pod,
the same can be achieved with offsetof() without storing the pointer
but it is IMO dangerous design).

To that end, I will use recursive template unions (berk).

// recursive template on depth from 0 to 3
// TSwizzleType is an helper defining the types o x,y,z,w at each
depth
// TContent will store the content of the data
template<class TContent, int depth=0,class TSwizzleType=swizzle_type >
union swizzle_container
{
TContent content;

swizzle_container<TContent,depth+1,typename TSwizzleType::x> x;
swizzle_container<TContent,depth+1,typename TSwizzleType::y> y;
swizzle_container<TContent,depth+1,typename TSwizzleType::z> z;
swizzle_container<TContent,depth+1,typename TSwizzleType::w> w;

typedef TSwizzleType type;
};

// end of recursion
template<class TContent,class TSwizzleType>
union swizzle_container<TContent,4,TSwizzleType>
{
TContent content;
typedef TSwizzleType type;
};

Note that whatever subtype x.y... you have, type gives you the
transformation and content is the same data storage.

Now we must define the type swizzle_type that will give us the
corresponding type for x,y,z,w. Each depth will be basically a
transformation of a swizzle_spec<> at and index (0,1,2,3) of a
corresponding index (0,1,2,3).
To that end I define the transform type operator:
template<int selindex, int selvalue, class TSwizzleSpec>
struct swizzle_select;

Which will be specialized for each index (boost integer typelist could
be a help to avoid this but I won't use them here).

template<int selvalue, class TSwizzleSpec>
struct swizzle_select<0,selvalue,TSwizzleSpec>
{
typedef swizzle_spec<
selvalue,
TSwizzleSpec::index1,
TSwizzleSpec::index2,
TSwizzleSpec::index3
};
template<int selvalue, class TSwizzleSpec>
struct swizzle_select<1,selvalue,TSwizzleSpec>
{
typedef swizzle_spec<
TSwizzleSpec::index0,
selvalue,
TSwizzleSpec::index2,
TSwizzleSpec::index3
};
template<int selvalue, class TSwizzleSpec>
struct swizzle_select<2,selvalue,TSwizzleSpec>
{
typedef swizzle_spec<
TSwizzleSpec::index0,
TSwizzleSpec::index1,
selvalue,
TSwizzleSpec::index3
};
template<int selvalue, class TSwizzleSpec>
struct swizzle_select<3,selvalue,TSwizzleSpec>
{
typedef swizzle_spec<
TSwizzleSpec::index0,
TSwizzleSpec::index1,
TSwizzleSpec::index2,
selvalue
};

Then the only operation left it to define the swizzle type with
recursive templated types:

template<int depth, class TSwizzleSpec>
struct swizzle_elt: TSwizzleSpec
{
typedef swizzle_elt<depth+1,typename swizzle_select<depth,
0,TSwizzleSpec>::type > x;
typedef swizzle_elt<depth+1,typename swizzle_select<depth,
1,TSwizzleSpec>::type > y;
typedef swizzle_elt<depth+1,typename swizzle_select<depth,
2,TSwizzleSpec>::type > z;
typedef swizzle_elt<depth+1,typename swizzle_select<depth,
3,TSwizzleSpec>::type > w;
};

// terminal case
template<class TSwizzleSpec>
struct swizzle_elt<4,TSwizzleSpec>:TSwizzleSpec
{
};

And my ivec4 class becomes:
struct ivec4{
int data[4];

swizzle_container<int*> swizzle;

ivec4():data(){swizzle.content=this->data;}

template<class TSwizzleContainer>
ivec4& operator=(const TSwizzleContainer& rhs)
{
typedef typename TSwizzleContainer::type TSwizzle;
data[0] = rhs.content[TSwizzle::index0];
data[1] = rhs.content[TSwizzle::index1];
data[2] = rhs.content[TSwizzle::index2];
data[3] = rhs.content[TSwizzle::index3];
return *this;
}
};

It is very long and in the wrong order but if you copy/paste in the
right order, you'll get a working example.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,776
Messages
2,569,603
Members
45,190
Latest member
ClayE7480

Latest Threads

Top