## Introduction

C++11 introduces `std::conditional`

to give C++ developer the flexibility to choose a type based on the compile-time condition.

template< bool B, class T, class F >
struct conditional;

If the boolean parameter of the `std::conditional`

is `true`

, then the delved type is `class T`

or else it is `class F`

. Below is an example on how to use `std::conditional`

. Before we use `std::conditional`

, we have to include `type_traits`

header. The `typeinfo`

header is included because of `typeid`

.

#include <iostream>
#include <type_traits>
#include <typeinfo>
int main()
{
typedef std::conditional<true, int, double>::type Type1;
typedef std::conditional<false, int, double>::type Type2;
std::cout << typeid(Type1).name() << '\n';
std::cout << typeid(Type2).name() << '\n';
}

Output of the program is given below:

int
double

## Applying to Endian Swapping

Let us apply what we have just learnt to implement endian swapping. We can determine whether to do endian swap at runtime. However, we can make that decision in compile time to get rid of the runtime check to stave off some execution time. The obvious disadvantage to this approach is we cannot change our decision during runtime. Let us delve into the code explanation. First, we define our `Endian enum`

.

enum class Endian
{
Big,
Little
};
using BigEndian = std::integral_constant<Endian, Endian::Big>;
using LittleEndian = std::integral_constant<Endian, Endian::Little>;

The `swap_endian_if_same_endian_is_false`

functions are defined below. The 2^{nd} parameter determines whether the endian of the platform and data are the same. If it is `false_type`

, then it must be swapped. We do nothing in the `true_type`

case.

template<typename T>
void swap_endian_if_same_endian_is_false(T& val, std::false_type)
{
std::is_arithmetic<T> is_arithmetic_type;
swap_endian_if_arithmetic(val, is_arithmetic_type);
}
template<typename T>
void swap_endian_if_same_endian_is_false(T& val, std::true_type)
{
}

We can determine the endian of the platform and data are the same with `std::is_same`

. For this article, we are not going to bother using `std::is_same`

. We short-circuit the check with `std::false_type`

to force swapping.

using same_endian_type = std::is_same<BigEndian, LittleEndian>;

In the `swap`

function mentioned above, we use `is_arithmetic`

to check the type is integer or floating point before calling `swap_endian_if_arithmetic`

. If `T`

is not arithmetic, it is a no-op.

template<typename T>
void swap_endian_if_arithmetic(T& val, std::true_type)
{
swap_endian(val, number_type<T>());
}
template<typename T>
void swap_endian_if_arithmetic(T& val, std::false_type)
{
}

These are the 5 overloaded `swap_endian`

functions.

template<typename T>
void swap_endian(T& ui, UnknownSize)
{
}
template<typename T>
void swap_endian(T& ui, SizeOf1)
{
}
template<typename T>
void swap_endian(T& ui, SizeOf8)
{
union EightBytes
{
T ui;
uint8_t arr[8];
};
EightBytes fb;
fb.ui = ui;
std::swap(fb.arr[0], fb.arr[7]);
std::swap(fb.arr[1], fb.arr[6]);
std::swap(fb.arr[2], fb.arr[5]);
std::swap(fb.arr[3], fb.arr[4]);
ui = fb.ui;
}
template<typename T>
void swap_endian(T& ui, SizeOf4)
{
union FourBytes
{
T ui;
uint8_t arr[4];
};
FourBytes fb;
fb.ui = ui;
std::swap(fb.arr[0], fb.arr[3]);
std::swap(fb.arr[1], fb.arr[2]);
ui = fb.ui;
}
template<typename T>
void swap_endian(T& ui, SizeOf2)
{
union TwoBytes
{
T ui;
uint8_t arr[2];
};
TwoBytes fb;
fb.ui = ui;
std::swap(fb.arr[0], fb.arr[1]);
ui = fb.ui;
}

Which `swap_endian`

function is selected by C++ compiler is determined by the 2^{nd} parameter type which is empty structure with a default constructor.

struct SizeOf1 { SizeOf1() { std::cout << "Size:1" << std::endl; } };
struct SizeOf2 { SizeOf2() { std::cout << "Size:2" << std::endl; } };
struct SizeOf4 { SizeOf4() { std::cout << "Size:4" << std::endl; } };
struct SizeOf8 { SizeOf8() { std::cout << "Size:8" << std::endl; } };
struct UnknownSize { UnknownSize() { std::cout << "Size:Unknown" << std::endl; } };

`number_type`

is alias template which makes use of nested `std::conditional`

to determine the type to be `SizeOf1`

, `SizeOf2`

, `SizeOf4`

, `SizeOf8`

or `UnknownSize`

based on the `sizeof(T)`

. `sizeof(T)`

is always evaluated at compile time.

template <class T>
using number_type =
typename std::conditional<
sizeof(T) == 1,
SizeOf1,
typename std::conditional<
sizeof(T) == 2,
SizeOf2,
typename std::conditional<
sizeof(T) == 4,
SizeOf4,
typename std::conditional<
sizeof(T) == 8,
SizeOf8,
UnknownSize
>::type
>::type
>::type
>::type;

Here is an example of swapping the integer twice. `std::false_type`

is specified for the 2^{nd} argument to force swapping.

int main()
{
int num = 1;
std::cout << num << std::endl;
swap_endian_if_same_endian_is_false(num, std::false_type());
std::cout << num << std::endl;
swap_endian_if_same_endian_is_false(num, std::false_type());
std::cout << num << std::endl;
return 0;
}

The output of the `main`

function is shown:

1
Size:4
16777216
Size:4
1

`std::conditional`

is a useful tool we can add to our arsenal when used judiciously. The repository for this article is at Github.

## References