Providing a method only in a partial specialization - c++

I have class Matrix and i want a specialized method of len() if R = 3 and C = 1.
I would like to have solution with the standard library.
Inheritance is not a solution because it wont work on the base class too.
Normal template specialization like
template<class T>
class Matrix<T, 3, 1> {
public:
T len() const {return 3;}
};
would force me to implement all other Matrix<T, R, C> methodes and stuff too. (?)
Ideas:
std::enable_if
constexpr
...
#include <vector>
template<class T, std::size_t R, std::size_t C>
class Matrix {
private:
std::vector <T> data;
public:
T len() const;
};
A dirty solution would be:
template<class T, std::size_t R, std::size_t C>
T Matrix<T, R, C>::len() const {
if constexpr (R == 3 && C == 1) {
return 3;
}
throw std::runtime_error("len not available");
}

A simple solution using std::enable_if.
#include <type_traits> // std::enable_if
#include <vector> // std::vector
template <class T, std::size_t R, std::size_t C>
class Matrix {
private:
std::vector<T> data;
public:
template <std::size_t C_ = C, typename = std::enable_if_t<C_ == 1>>
std::size_t len() const {
double res = 0;
for (const auto& x : data) {
res += std::pow(x, 2);
}
return std::sqrt(res);
}
};
Alternative out-of-line definition:
template <class T, std::size_t R, std::size_t C>
class Matrix {
private:
std::vector<T> data;
public:
template <std::size_t R_ = R,
std::size_t C_ = C,
typename E1 = std::enable_if_t<R_ == 3>,
typename E2 = std::enable_if_t<C_ == 1>>
double len() const;
};
template <class T, std::size_t R, std::size_t C>
template <std::size_t R_, std::size_t C_, typename E1, typename E2>
double Matrix<T, R, C>::len() const {
double res = 0;
for (const auto& x : data) {
res += std::pow(x, 2);
}
return std::sqrt(res);
}
Example:
#include <iostream> // std::cout, std::endl
int main(int argc, char* argv[]) {
Matrix<double, 3, 1> vec;
std::cout << "vec length: " << vec.len() << std::endl;
// output: vec length: 3
// Matrix<double, 3, 3> mat;
// std::cout << "mat length: " << mat.len() << std::endl; // does not compile
return 0;
}

One possible approach:
Explanation inline as comments:
#include <iostream>
template<class T, int R, int C>
struct Matrix;
namespace MatrixOperations
{
// default handling is to prevent compilation
template<class Mat>
auto len(Mat const&) -> void = delete;
// special case for a 3,1 matrix
template<class T>
auto len(Matrix<T, 3, 1> const&) -> T
{
return T(3);
}
};
template<class T, int R, int C>
struct Matrix
{
// ForceDeduce is a defaulted type.
// The argument of this type is defaulted
// It's purpose is to ensure that this function is not actually compiled until it is used
// This will prevent the call to MatrixOperations::len producing an error unless this function
// is called
template<class ForceDeduce = int*>
T len(ForceDeduce = nullptr) const
{
return MatrixOperations::len(*this);
}
};
int main()
{
Matrix <int, 3, 1> m31;
std::cout << m31.len() << std::endl;
Matrix <int, 3, 2> m32;
// fails to compile
// std::cout << m32.len() << std::endl;
}

Related

C++, change data type of all the elements into a nested C array

i am trying to change the data type of all the elements into a nested C array, something like this.
const int a[2][3] = {
{1,2,3},
{4,5,6}
}
The arrays are "multidimensional", and i don't know how many dimension they have.
I figured out something like this:
template <class D, class T, unsigned S>
inline D& uniform(T (&t)[S]) {
D v[S];
for (int k = 0; k < S; k++) {
v[k] = D(t[k]);
}
return v;
}
auto b = uniform<float>( a );
However the previous code works (or at least it is supposed to work) only if a is 1D, is there a way to make it work over multidimensional C arrays?
So, here's one way of doing it:
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdio>
#include <type_traits>
// array_type_swap convert T[a][b][c][...] to Y[a][b][c][...] for arbitrary
// dimensions
template <class T, class Y>
struct array_type_swap {
using type = T;
};
template <class T, class Y, std::size_t N>
struct array_type_swap<T, std::array<Y, N>> {
using type = std::array<typename array_type_swap<T, Y>::type, N>;
};
template <class T, class Y, std::size_t N>
struct array_type_swap<T, Y[N]> {
using type = typename array_type_swap<T, std::array<Y, N>>::type;
};
template <class T, class Y>
using array_type_swap_t = typename array_type_swap<T, Y>::type;
template <class>
inline constexpr bool is_std_array_v = false;
template <class T, std::size_t N>
inline constexpr bool is_std_array_v<std::array<T, N>> = true;
// Get element count of a std::array, or C style array as constexpr. The value
// is 1 for all other types (as in array of 1).
template <class T>
struct contexpr_array_size {
static std::size_t constexpr size = 1;
};
template <class T, std::size_t N>
struct contexpr_array_size<T[N]> {
static std::size_t constexpr size = N;
};
template <class T, std::size_t N>
struct contexpr_array_size<std::array<T, N>> {
static std::size_t constexpr size = N;
};
template <class T>
inline auto constexpr contexpr_array_size_v = contexpr_array_size<T>::size;
template <class T, class Y>
void copy_array(T& dst, Y const& src) {
static_assert(contexpr_array_size_v<T> == contexpr_array_size_v<Y>,
"Can only copy arrays of the same size");
if constexpr (!is_std_array_v<Y> && !std::is_array_v<Y>)
dst = static_cast<T>(src);
else
for (std::size_t i = 0; i < contexpr_array_size_v<Y>; ++i)
copy_array(dst[i], src[i]);
}
template <class T, class Y>
auto uniform(Y const& arr) {
array_type_swap_t<T, Y> result;
copy_array(result, arr);
return result;
}
int main() {
std::puts("built-in array :");
const int a[2][3] = {{1, 2, 3}, {4, 5, 6}};
auto b = uniform<float>(a);
for (auto& row : b) {
for (auto& elm : row) std::printf("%.2f ", elm);
std::putchar('\n');
}
std::puts("\nstd::array :");
std::array<std::array<int, 3>, 2> stdarr = {{{6, 5, 4}, {3, 2, 1}}};
b = uniform<float>(stdarr);
for (auto& row : b) {
for (auto& elm : row) std::printf("%.2f ", elm);
std::putchar('\n');
}
}
You can't return [] arrays from a function, so the 1D version doesn't work.
You can use std::array.
template <class D, class T, std::size_t S1, std::size_t S2>
inline std::array<std::array<D, S1>, S2> uniform(std::array<std::array<T, S1>, S2> t) {
std::array<std::array<D, S1>, S2> v;
std::array<D, S1>(*inner)(std::array<T, S1>) = uniform<D, T, S1>;
std::transform(t.begin(), t.end(), v.begin(), inner);
return v;
}
template <class D, class T, std::size_t S>
inline std::array<D, S> uniform(std::array<T, S> t) {
return { t.begin(), t.end() };
}

How to use C++ template magic in order to pattern match on type

The desire is to have only one wrapper for all typename T which support structured bindings for example via tuple_size and tuple_element without runtime overhead(contexprs, SFINAE). There's a function encode with accepts T obj as an argument and calls encode_impl with more specific arguments and type arguments.
The article https://playfulprogramming.blogspot.com/2016/12/serializing-structs-with-c17-structured.html uses a bunch of arity functions to achieve the same result. But as far as I understand tuples provide std::tuple_size which is possible to utilize.
#include <tuple>
#include <utility>
class Aa {
public:
Aa(int a1_, int a2_): a1(a1_), a2(a2_) {}
template<std::size_t N>
decltype(auto) get() const {
if constexpr (N == 0) return a1;
else if constexpr (N == 1) return a2;
}
private:
int a1;
int a2;
};
class Bb {
public:
Bb(Aa a_, int b_): a(a_), b(b_) {}
template<std::size_t N>
decltype(auto) get() const {
if constexpr (N == 0) return a;
else if constexpr (N == 1) return b;
}
private:
Aa a;
int b;
};
namespace std {
// Aa
template<>
struct tuple_size<Aa> : std::integral_constant<std::size_t, 2> {};
template<std::size_t N>
struct tuple_element<N, Aa> {
using type = decltype(std::declval<Aa>().get<N>());
};
// Bb
template<>
struct tuple_size<Bb> : std::integral_constant<std::size_t, 2> {};
template<std::size_t N>
struct tuple_element<N, Bb> {
using type = decltype(std::declval<Bb>().get<N>());
};
}
template <size_t N>
using size = std::integral_constant<size_t, N>;
template<typename T>
void encode(T t) {
encode_impl<?std::tuple_size<T>?()>(T t, ?std::tuple_size<T>);
}
template<?>
encode_impl(T t, ?) {
std::cout << "It works";
}
The expectations of resolving the issue are the understanding of notation which must be used to compile the code snippet. Right now you can see ? in many places.
If it's not possible to do using std::tuple_size then alternative solutions are welcome. Arity function from the article for classes don't work but that's a bit different question.
Are you asking how to apply the return values of some_type::get<i>() as the arguments of a function?
Off the top of my head you would write encode and encode_impl something like this:
template<typename T>
void encode(T const& t, std::ostream& os)
{
encode_impl(t, os, std::make_index_sequence<std::tuple_size<T>::value>{ });
}
template
<typename T, std::size_t... I>
void encode_impl(T const& t, std::ostream& os, std::index_sequence<I...> const)
{
constexpr auto last = sizeof...(I) - 1;
os << "{ ";
[[maybe_unused]] int const temp[] =
{ ((os << t.template get<I>() << (I != last ? ", " : " ")), 0)... };
os << "}" << std::endl;
}
This disgusting looking pack expansion in encode_impl is using the comma operator just to force evaluation of its left hand operand and discard the result, then evaluate the literal 0 and store it in the dummy array temp. The pack expansion is used to initialize an array so that the arguments are evaluated in the correct order (left to right).
UPDATE:
Okay so I think what you want is to make your own type trait test is_tuple_like that returns true if the type T is "tuple-like" which just requires that the expression std::tuple_size<T>::value is well formed and the expression declval<T&>().template get<std::size_t(0)>() is well formed. From there you can write a function that prints the elements of "tuple-like" types and if any of those elements are tuple-like, print their elements, recursively. Here's what I came up with:
template
<
typename T,
typename tp_enabled =
std::void_t
<
decltype(std::tuple_size<T>::value),
decltype(std::declval<T&>().template get<std::size_t(0)>())
>
>
constexpr auto
is_tuple_like(int const)noexcept->bool
{
return true;
}
template
<typename T, typename tp_arg>
constexpr auto
is_tuple_like(tp_arg const)noexcept->bool
{
return false;
}
template<typename T>
auto encode(T const& t, std::ostream& os)->
std::enable_if_t<is_tuple_like<T>(0)>
{
encode_impl(t, os, std::make_index_sequence<std::tuple_size<T>::value>{ });
os << std::endl;
}
template
<bool is_last, typename T>
auto
encode_one(T const& t, std::ostream& os)->
std::enable_if_t<!is_tuple_like<T>(0)>
{
os << t << (is_last ? " " : ", ");
}
template
<bool is_last, typename T>
auto
encode_one(T const& t, std::ostream& os)->
std::enable_if_t<is_tuple_like<T>(0)>
{
encode_impl(t, os, std::make_index_sequence<std::tuple_size<T>::value>{ });
os << (is_last ? " " : ", ");
}
template
<typename T, std::size_t... I>
void encode_impl(T const& t, std::ostream& os, std::index_sequence<I...> const)
{
constexpr auto last = sizeof...(I) - 1;
os << "{ ";
[[maybe_unused]] int const temp[] =
{ (encode_one<I == last>(t.template get<I>(), os), 0)... };
os << "}";
}
int main () {
auto a = Aa(1, 1);
encode(a, std::cout);
auto b = Bb(a, 1);
encode(b, std::cout);
return 0;
}
OUTPUT:
{ 1, 1 }
{ { 1, 1 }, 1 }
UPDATE 2: So it turns out the above implementation of is_tuple_like compiles fine on GCC and the latest version of Clang (8.0.0) but fails to compile on Clang 7.0.0 so here is a version that works on Clang 7.0.0 it uses a variable template instead of a function template:
#include <tuple>
#include <utility>
#include <iostream>
class Aa {
public:
Aa(int a1_, int a2_): a1(a1_), a2(a2_) {}
template<std::size_t N>
decltype(auto) get() const {
if constexpr (N == 0) return a1;
else if constexpr (N == 1) return a2;
}
private:
int a1;
int a2;
};
class Bb {
public:
Bb(Aa a_, int b_): a(a_), b(b_) {}
template<std::size_t N>
decltype(auto) get() const {
if constexpr (N == 0) return a;
else if constexpr (N == 1) return b;
}
private:
Aa a;
int b;
};
namespace std {
// Aa
template<>
struct tuple_size<Aa> : std::integral_constant<std::size_t, 2> {};
template<std::size_t N>
struct tuple_element<N, Aa> {
using type = decltype(std::declval<Aa>().get<N>());
};
// Bb
template<>
struct tuple_size<Bb> : std::integral_constant<std::size_t, 2> {};
template<std::size_t N>
struct tuple_element<N, Bb> {
using type = decltype(std::declval<Bb>().get<N>());
};
}
template
<typename T, typename tp_enabled = std::void_t<>>
constexpr bool is_tuple_like = false;
template
<typename T>
constexpr bool
is_tuple_like
<
T,
std::void_t
<
decltype(std::tuple_size<T>::value),
decltype(std::declval<T&>().template get<std::size_t(0)>())
>
> = true;
template<typename T>
auto encode(T const& t, std::ostream& os)->
std::enable_if_t<is_tuple_like<T>>
{
encode_impl(t, os, std::make_index_sequence<std::tuple_size<T>::value>{ });
os << std::endl;
}
template
<bool is_last, typename T>
auto
encode_one(T const& t, std::ostream& os)->
std::enable_if_t<!is_tuple_like<T>>
{
os << t << (is_last ? " " : ", ");
}
template
<bool is_last, typename T>
auto
encode_one(T const& t, std::ostream& os)->
std::enable_if_t<is_tuple_like<T>>
{
encode_impl(t, os, std::make_index_sequence<std::tuple_size<T>::value>{ });
os << (is_last ? " " : ", ");
}
template
<typename T, std::size_t... I>
void encode_impl(T const& t, std::ostream& os, std::index_sequence<I...> const)
{
constexpr auto last = sizeof...(I) - 1;
os << "{ ";
[[maybe_unused]] int const temp[] =
{ (encode_one<I == last>(t.template get<I>(), os), 0)... };
os << "}";
}
int main () {
auto a = Aa(1, 1);
encode(a, std::cout);
auto b = Bb(a, 1);
encode(b, std::cout);
return 0;
}

Make longer std::array accessible as if it's shorter

I am implementing my static multi-dimentional vector class. I am using std::array as the underlying data type.
template <typename T, std::size_t N>
class Vector {
private:
std::array<T, N> data;
};
I want to make my class downwards-compatible, so I am writing this:
template <typename T, std::size_t N>
class Vector : public Vector<T, N-1>{
private:
std::array<T, N> data;
};
template <typename T>
class Vector<T, 0> {};
My goal is that when one instance is used in downwards-compatible mode, its underlying data should be able to be reliably accessed:
template<typename T, std::size_t N>
T& Vector<T, N>::operator[](int i) {
// Do boundary checking here
return this->data[i];
}
void foo(Vector<int, 3>& arg) {
arg[1] = 10;
}
Vector<int, 5> b;
foo(b);
// Now b[1] should be 10
There are two points here:
Vector<T, 5> should be accepted by foo(), Vector<T, 2> should be rejected.
Changes to b[0] through b[2] in foo() should pertain. b[3] and b[4] should not be accessible in foo().
How can I achieve that?
How about a simple read wrapper around std::array<> itself?
template<typename T, std::size_t N>
struct ArrayReader {
public:
// Intentionally implicit.
template<std::size_t SRC_LEN>
ArrayReader(std::array<T, SRC_LEN> const& src)
: data_(src.data()) {
static_assert(SRC_LEN >= N);
}
private:
T const* data_;
};
void foo(ArrayReader<float, 3>);
void bar() {
std::array<float, 4> a;
std::array<float, 2> b;
foo(a);
foo(b); //BOOM!
}
Of course, you can easily substitute std::array for your own type, this is just an example of the principle.
Have array keep the data, and then create additional non-owning class, e.g. array_view that will keep only a pointer. It will have generic constructor that accepts the array, and will have a static_assert to check the sizes.
Here's how I would approach this:
template <class Container, std::size_t size>
struct range_view
{
range_view(Container * p): container(p) { assert(size <= p->size()); }
auto & operator[](std::size_t i) { return (*container)[i]; }
private:
Container * container;
};
Then you simply define foo as:
template <class C>
void foo(range_view<C, 3> c)
{
c[1] = 1;
}
Here's something that is closest to what I think you would need.
Make Vector a viewer/user of the data, not the owner of the data.
#include <array>
template <typename T, std::size_t N, std::size_t I>
class Vector : public Vector<T, N, I-1>
{
public:
Vector(std::array<T, N>& arr) : Vector<T, N, I-1>(arr), arr_(arr) {}
T& operator[](int i) {
return arr_[i];
}
private:
std::array<T, N>& arr_;
};
template <typename T, std::size_t N>
class Vector<T, N, 0ul>
{
public:
Vector(std::array<T, N>& arr) : arr_(arr) {}
private:
std::array<T, N>& arr_;
};
void foo(Vector<int, 5, 3>& arg) {
arg[1] = 10;
// Can't find a way to make this a compile time error.
arg[3] = 10;
}
#include <iostream>
int main()
{
std::array<int, 5> arr;
Vector<int, 5, 5> b(arr);
foo(b);
std::cout << b[1] << std::endl;
}
Here's a demonstration of how to implement the Vector class that you tried in your question. At each level you only store 1 value instead of an array and that way when you compose all your N Arrays together you get space for N values. Of course then calling operator[] gets tricky, which is the meat of what I wanted to demonstrate.
#include <utility>
template <class T, std::size_t N>
struct Array : Array<T, N-1>
{
T & operator[](std::size_t i)
{
return const_cast<T&>((*const_cast<const Array*>(this))[i]);
}
const T & operator[](std::size_t i) const
{
return Get(i, std::make_index_sequence<N>());
}
template <std::size_t i>
const T & Geti() const
{
return static_cast<const Array<T, i+1>&>(*this).GetValue();
}
const T & GetValue() const { return value; }
template <std::size_t ... indices>
const T & Get(std::size_t i, std::integer_sequence<std::size_t, indices...>) const
{
using X = decltype(&Array::Geti<0>);
X getters[] = { &Array::Geti<indices>... };
return (this->*getters[i])();
}
template <std::size_t i, class = typename std::enable_if<(i <= N)>::type>
operator Array<T, i>&() { return (Array<T, i>&)*this; }
private:
T value;
};
template <class T>
struct Array<T, 0>{};
void foo(Array<float, 3> & a) { a[1] = 10; }
int main()
{
Array<float, 10> a;
foo(a);
}

Differentiate between 1D and 2D container in template class constructor (SFINAE)

So, I have a class, which has an array of arrays as a private member. I wish to have two constructors for each case (1D or 2D). But of course their declaration happens to be the same, so template deduction can't do its job without me doing something about it. Here's the code:
Edit: I also need it to work with STL containers like vector or C++ array. That is why I am overcomplicating and not going with the "arrays" fix.
#include <iostream>
#include <array>
template<class T, std::size_t rows_t, std::size_t cols_t>
class test
{
private:
std::array<std::array<T, cols_t>, rows_t> _data;
public:
auto begin() { return this->_data.begin(); }
auto end() { return this->_data.end(); }
//CONSTRUCTOR
template<class type_t>
test(const type_t &arr)
{
std::size_t j = 0;
for (const auto &num : arr)
this->_data[0][j++] = num;
}
template<class type_t>
test(const type_t &arr)
{
std::size_t i = 0;
for (const auto &el : arr)
{
std::size_t j = 0;
for (const auto &num : el)
this->_data[i][j++] = num;
++i;
}
}
};
int main()
{
double arr[3] = { 1, 2, 3 };
double arr2[2][2] = { {1, 2}, {3, 4} };
test<double, 1, 3> obj = arr;
test<double, 2, 2> obj2 = arr2;
for (const auto &i : obj2)
{
for (const auto &j : i)
std::cout << j << " ";
std::cout << std::endl;
}
std::cin.get();
}
Note: I've been reading about enable_if, but I don't quite understand how it works. Can it be done with that?
The constructors should not be the same, but you have only provided the most generic matching possible.
SFINAE is not necessary here. Just provide a constructor for a 1D array, and a separate constructor for a 2D array:
template <typename T2, std::size_t N>
test( const T2 (&a)[N] )
{
...
}
template <typename T2, std::size_t M, std::size_t N>
test( const T2 (&a)[M][N] )
{
...
}
Another note: POSIX reserves typenames ending with "_t", so it is typically a good idea to avoid them in your own code. (Obnoxious, I know.) Standard C++ will use Camel Case of the form: RowsType, etc, and then typedef a rows_type for users of the class.
Notice, however, that rows_t is not actually a type -- it is a value. A better name would be something like NRows.
Hope this helps.
First, you have to "teach" the compiler what's 2D and what's not. Hence, you have to define something like the following type trait:
template<typename T>
struct is2D : public std::false_type {};
template<typename T, std::size_t N, std::size_t M>
struct is2D<std::array<std::array<T, M>, N>> : std::true_type {};
template<typename T>
struct is2D<std::vector<std::vector<T>>> : std::true_type {};
template<typename T, std::size_t N, std::size_t M>
struct is2D<T[N][M]> : std::true_type {};
Then you could set up your class definition in the following way:
template<class T, std::size_t rows_t, std::size_t cols_t>
class test{
std::array<std::array<T, cols_t>, rows_t> _data;
template<class type_t>
std::enable_if_t<!is2D<type_t>::value, void>
test_init(type_t const &arr) {
std::size_t j = 0;
for (const auto &num : arr) _data[0][j++] = num;
}
template<class type_t>
std::enable_if_t<is2D<type_t>::value, void>
test_init(type_t const &arr) {
std::size_t i = 0;
for(const auto &el : arr) {
std::size_t j = 0;
for (const auto &num : el) _data[i][j++] = num;
++i;
}
}
public:
auto &operator[](const std::size_t &i) { return this->_data[i]; }
auto begin() { return this->_data.begin(); }
auto end() { return this->_data.end(); }
//CONSTRUCTOR
template<class type_t> test(type_t const &arr) { test_init(arr); }
};
LIVE DEMO

Switch statement variadic template expansion

Let me please consider the following synthetic example:
inline int fun2(int x) {
return x;
}
inline int fun2(double x) {
return 0;
}
inline int fun2(float x) {
return -1;
}
int fun(const std::tuple<int,double,float>& t, std::size_t i) {
switch(i) {
case 0: return fun2(std::get<0>(t));
case 1: return fun2(std::get<1>(t));
case 2: return fun2(std::get<2>(t));
}
}
The question is how should I expand this to the general case
template<class... Args> int fun(const std::tuple<Args...>& t, std::size_t i) {
// ?
}
Guaranteeing that
fun2 can be inlined into fun
search complexity not worse than O(log(i)) (for large i).
It is known that optimizer usually uses lookup jump table or compile-time binary search tree when large enough switch expanded. So, I would like to keep this property affecting performance for large number of items.
Update #3: I remeasured performance with uniform random index value:
1 10 20 100
#TartanLlama
gcc ~0 42.9235 44.7900 46.5233
clang 10.2046 38.7656 40.4316 41.7557
#chris-beck
gcc ~0 37.564 51.3653 81.552
clang ~0 38.0361 51.6968 83.7704
naive tail recursion
gcc 3.0798 40.6061 48.6744 118.171
clang 11.5907 40.6197 42.8172 137.066
manual switch statement
gcc 41.7236
clang 7.3768
Update #2: It seems that clang is able to inline functions in #TartanLlama solution whereas gcc always generates function call.
Ok, I rewrote my answer. This gives a different approach to what TartanLlama and also what I suggested before. This meets your complexity requirement and doesn't use function pointers so everything is inlineable.
Edit: Much thanks to Yakk for pointing out a quite significant optimization (for the compile-time template recursion depth required) in comments
Basically I make a binary tree of the types / function handlers using templates, and implement the binary search manually.
It might be possible to do this more cleanly using either mpl or boost::fusion, but this implementation is self-contained anyways.
It definitely meets your requirements, that the functions are inlineable and runtime look up is O(log n) in the number of types in the tuple.
Here's the complete listing:
#include <cassert>
#include <cstdint>
#include <tuple>
#include <iostream>
using std::size_t;
// Basic typelist object
template<typename... TL>
struct TypeList{
static const int size = sizeof...(TL);
};
// Metafunction Concat: Concatenate two typelists
template<typename L, typename R>
struct Concat;
template<typename... TL, typename... TR>
struct Concat <TypeList<TL...>, TypeList<TR...>> {
typedef TypeList<TL..., TR...> type;
};
template<typename L, typename R>
using Concat_t = typename Concat<L,R>::type;
// Metafunction First: Get first type from a typelist
template<typename T>
struct First;
template<typename T, typename... TL>
struct First <TypeList<T, TL...>> {
typedef T type;
};
template<typename T>
using First_t = typename First<T>::type;
// Metafunction Split: Split a typelist at a particular index
template<int i, typename TL>
struct Split;
template<int k, typename... TL>
struct Split<k, TypeList<TL...>> {
private:
typedef Split<k/2, TypeList<TL...>> FirstSplit;
typedef Split<k-k/2, typename FirstSplit::R> SecondSplit;
public:
typedef Concat_t<typename FirstSplit::L, typename SecondSplit::L> L;
typedef typename SecondSplit::R R;
};
template<typename T, typename... TL>
struct Split<0, TypeList<T, TL...>> {
typedef TypeList<> L;
typedef TypeList<T, TL...> R;
};
template<typename T, typename... TL>
struct Split<1, TypeList<T, TL...>> {
typedef TypeList<T> L;
typedef TypeList<TL...> R;
};
template<int k>
struct Split<k, TypeList<>> {
typedef TypeList<> L;
typedef TypeList<> R;
};
// Metafunction Subdivide: Split a typelist into two roughly equal typelists
template<typename TL>
struct Subdivide : Split<TL::size / 2, TL> {};
// Metafunction MakeTree: Make a tree from a typelist
template<typename T>
struct MakeTree;
/*
template<>
struct MakeTree<TypeList<>> {
typedef TypeList<> L;
typedef TypeList<> R;
static const int size = 0;
};*/
template<typename T>
struct MakeTree<TypeList<T>> {
typedef TypeList<> L;
typedef TypeList<T> R;
static const int size = R::size;
};
template<typename T1, typename T2, typename... TL>
struct MakeTree<TypeList<T1, T2, TL...>> {
private:
typedef TypeList<T1, T2, TL...> MyList;
typedef Subdivide<MyList> MySubdivide;
public:
typedef MakeTree<typename MySubdivide::L> L;
typedef MakeTree<typename MySubdivide::R> R;
static const int size = L::size + R::size;
};
// Typehandler: What our lists will be made of
template<typename T>
struct type_handler_helper {
typedef int result_type;
typedef T input_type;
typedef result_type (*func_ptr_type)(const input_type &);
};
template<typename T, typename type_handler_helper<T>::func_ptr_type me>
struct type_handler {
typedef type_handler_helper<T> base;
typedef typename base::func_ptr_type func_ptr_type;
typedef typename base::result_type result_type;
typedef typename base::input_type input_type;
static constexpr func_ptr_type my_func = me;
static result_type apply(const input_type & t) {
return me(t);
}
};
// Binary search implementation
template <typename T, bool b = (T::L::size != 0)>
struct apply_helper;
template <typename T>
struct apply_helper<T, false> {
template<typename V>
static int apply(const V & v, size_t index) {
assert(index == 0);
return First_t<typename T::R>::apply(v);
}
};
template <typename T>
struct apply_helper<T, true> {
template<typename V>
static int apply(const V & v, size_t index) {
if( index >= T::L::size ) {
return apply_helper<typename T::R>::apply(v, index - T::L::size);
} else {
return apply_helper<typename T::L>::apply(v, index);
}
}
};
// Original functions
inline int fun2(int x) {
return x;
}
inline int fun2(double x) {
return 0;
}
inline int fun2(float x) {
return -1;
}
// Adapted functions
typedef std::tuple<int, double, float> tup;
inline int g0(const tup & t) { return fun2(std::get<0>(t)); }
inline int g1(const tup & t) { return fun2(std::get<1>(t)); }
inline int g2(const tup & t) { return fun2(std::get<2>(t)); }
// Registry
typedef TypeList<
type_handler<tup, &g0>,
type_handler<tup, &g1>,
type_handler<tup, &g2>
> registry;
typedef MakeTree<registry> jump_table;
int apply(const tup & t, size_t index) {
return apply_helper<jump_table>::apply(t, index);
}
// Demo
int main() {
{
tup t{5, 1.5, 15.5f};
std::cout << apply(t, 0) << std::endl;
std::cout << apply(t, 1) << std::endl;
std::cout << apply(t, 2) << std::endl;
}
{
tup t{10, 1.5, 15.5f};
std::cout << apply(t, 0) << std::endl;
std::cout << apply(t, 1) << std::endl;
std::cout << apply(t, 2) << std::endl;
}
{
tup t{15, 1.5, 15.5f};
std::cout << apply(t, 0) << std::endl;
std::cout << apply(t, 1) << std::endl;
std::cout << apply(t, 2) << std::endl;
}
{
tup t{20, 1.5, 15.5f};
std::cout << apply(t, 0) << std::endl;
std::cout << apply(t, 1) << std::endl;
std::cout << apply(t, 2) << std::endl;
}
}
Live on Coliru:
http://coliru.stacked-crooked.com/a/3cfbd4d9ebd3bb3a
If you make fun2 into a class with overloaded operator():
struct fun2 {
inline int operator()(int x) {
return x;
}
inline int operator()(double x) {
return 0;
}
inline int operator()(float x) {
return -1;
}
};
then we can modify dyp's answer from here to work for us.
Note that this would look a lot neater in C++14, as we could have all the return types deduced and use std::index_sequence.
//call the function with the tuple element at the given index
template<class Ret, int N, class T, class Func>
auto apply_one(T&& p, Func func) -> Ret
{
return func( std::get<N>(std::forward<T>(p)) );
}
//call with runtime index
template<class Ret, class T, class Func, int... Is>
auto apply(T&& p, int index, Func func, seq<Is...>) -> Ret
{
using FT = Ret(T&&, Func);
//build up a constexpr array of function pointers to index
static constexpr FT* arr[] = { &apply_one<Ret, Is, T&&, Func>... };
//call the function pointer at the specified index
return arr[index](std::forward<T>(p), func);
}
//tag dispatcher
template<class Ret, class T, class Func>
auto apply(T&& p, int index, Func func) -> Ret
{
return apply<Ret>(std::forward<T>(p), index, func,
gen_seq<std::tuple_size<typename std::decay<T>::type>::value>{});
}
We then call apply and pass the return type as a template argument (you could deduce this using decltype or C++14):
auto t = std::make_tuple(1,1.0,1.0f);
std::cout << apply<int>(t, 0, fun2{}) << std::endl;
std::cout << apply<int>(t, 1, fun2{}) << std::endl;
std::cout << apply<int>(t, 2, fun2{}) << std::endl;
Live Demo
I'm not sure if this will completely fulfil your requirements due to the use of function pointers, but compilers can optimize this kind of thing pretty aggressively. The searching will be O(1) as the pointer array is just built once then indexed directly, which is pretty good. I'd try this out, measure, and see if it'll work for you.