Consider a function object F
taking a constexpr size_t
argument I
struct F
{
template
constex
I'll take the obvious position here and ask if "I want to emphasize that it is constexpr
by using it as a template argument" is worth this cost and if:
struct F
{
constexpr size_t operator()(size_t i) const { return i; }
template <size_t I>
constexpr size_t operator()(size <I>) const { return (*this)(I); }
};
would not be a much simpler solution.
This is not exactly an answer and my question still stands, yet I have found a workaround that gives an impressive boost in compilation. It is a minor tweak of the solution given in the question, where parameter R
is moved from operator()
outside to structure idx
, and the terminating condition now includes both R
and N
:
template <
typename F, size_t L,
size_t R = 1, size_t N = 0, bool = (R < 2 * L && N < L)
>
struct idx //...
The entire code is given in this new live example.
This approach apparently cuts down a huge number of unnecessary specialization combinations for R
. Compile times and executable sizes drop dramatically. For instance, I have been able to compile in 10.7/18.7 seconds with Clang/GCC for L = 1<<12
(4096), yielding an executable of 220/239 KB. In this case, nm -C
shows 546/250 versions of operator()
.
I call this technique the magic switch.
The most efficient way I know of to do this is to build your own jump table.
// first, index list boilerplate. Does log-depth creation as well
// needed for >1000 magic switches:
template<unsigned...Is> struct indexes {typedef indexes<Is...> type;};
template<class lhs, class rhs> struct concat_indexes;
template<unsigned...Is, unsigned...Ys> struct concat_indexes<indexes<Is...>, indexes<Ys...>>{
typedef indexes<Is...,Ys...> type;
};
template<class lhs, class rhs>
using ConcatIndexes = typename concat_indexes<lhs, rhs>::type;
template<unsigned min, unsigned count> struct make_indexes:
ConcatIndexes<
typename make_indexes<min, count/2>::type,
typename make_indexes<min+count/2, (count+1)/2>::type
>
{};
template<unsigned min> struct make_indexes<min, 0>:
indexes<>
{};
template<unsigned min> struct make_indexes<min, 1>:
indexes<min>
{};
template<unsigned max, unsigned min=0>
using MakeIndexes = typename make_indexes<min, max-min>::type;
// This class exists simply because [](blah){code}... `...` expansion
// support is lacking in many compilers:
template< typename L, typename R, unsigned I >
struct helper_helper {
static R func( L&& l ) { return std::forward<L>(l)(size<I>()); }
};
// the workhorse. Creates an "manual" jump table, then invokes it:
template<typename L, unsigned... Is>
auto
dispatch_helper(indexes<Is...>, L&& l, unsigned i)
-> decltype( std::declval<L>()(size<0>()) )
{
// R is return type:
typedef decltype( std::declval<L>()(size<0>()) ) R;
// F is the function pointer type for our jump table:
typedef R(*F)(L&&);
// the jump table:
static const F table[] = {
helper_helper<L, R, Is>::func...
};
// invoke at the jump spot:
return table[i]( std::forward<L>(l) );
};
// create an index pack for each index, and invoke the helper to
// create the jump table:
template<unsigned Max, typename L>
auto
dispatch(L&& l, unsigned i)
-> decltype( std::declval<L>()(size<0>()) )
{
return dispatch_helper( MakeIndexes<Max>(), std::forward<L>(l), i );
};
which requires some static setup, but is pretty fast when run.
An assert that i
is in bounds may also be useful.
live example
If your solution have cap on maximum possible value (say 256) you can use macro magic and switch statement to simplify it:
#define POS(i) case (i): return F<(i)>(); break;
#define POS_4(i) POS(i + 0) POS(i + 1) POS(i + 2) POS(i + 3)
#define POS_16(i) POS_4(i + 0) POS_4(i + 4) POS_4(i + 8) POS_4(i + 12)
int func(int i)
{
switch(i)
{
POS_16(0)
}
}
Another possible solution is (with C++11) use variadic templates:
template<int I>
struct FF
{
static int f() { return I; }
};
template<typename... I>
int func(int i)
{
constexpr int (*Func[])() = { I::f... };
return Func[i]();
}
int main(int argc, char** argv)
{
func<FF<0>,FF<1>>(1);
}