14

Long time back I had seen a non-recursive implementation to get the last value/type from a type sequence/value sequence. It has a nice property, that the number of template instantiated is independent (and constant) of the number of elements the sequence contains.

The implementation is simple, as follows

// a struct that eats anything and everything
struct eat { template<class T> eat(T&&) {} }; 
// generates V matching with U
template<class U, class V> struct match { using type = V; }; 
template<class... X> struct back_ 
{ 
    template<class U>
    static U&& get(typename match<X, eat>::type..., U&& u)
    {
        return static_cast<U&&>(u); // forward
    }
};
// simple macro to avoid repetition for trailing return type.
#define RETURNS(exp) -> decltype(exp) { return exp; }
// get the last value in meta O(1) 
template<class T, class... Ts>
auto back(T&& t, Ts&&... ts) RETURNS( back_<Ts...>::get(static_cast<T&&>(t), static_cast<Ts&&>(ts)...))

It uses a simple fact that given a variadic type X... the compiler can non-recursively generate another type T as many as Xs are there.

So, I want to know is there a way to extend it to implement at_c or nth function with constant number of instantiated templates (independent of number of elements).

It also may be phrased as, give a variadic type X... and some integer N, is it possible to non-recursively generate a sub-sequence of X... consisting of N elements?

David G
  • 94,763
  • 41
  • 167
  • 253
abir
  • 1,797
  • 14
  • 26
  • You can just provide an infinite amount of overloads, which manually have 1, 2, 3, 4, 6, ... first parameters and stuff. Other than that, the best you can get is `log N` I believe. – Xeo Jun 13 '13 at 13:10
  • @Xeo You should be able to pull off `log log N` with a sufficiently fast growing recursion? Pretty pointless, as a 1000 recursion limit under `log N` is nigh infinitely far away, and the overhead of pulling off `log log N` recursion depth would be higher than the difference for any reasonably `N`... – Yakk - Adam Nevraumont Jun 13 '13 at 13:39
  • 4
    Please stop using `static_cast` like that. We have [`std::forward`](http://en.cppreference.com/w/cpp/utility/forward) for a reason: it's much more legible and actually tells the reader *why* you're doing it, unlike `static_cast` which is arcane and meaningless to those not in the know. – Nicol Bolas Jun 13 '13 at 15:01
  • Even I think it is the right answer I'll delete it. May then attract more MP gurus to this quest. Nice question though. – ritter Jun 13 '13 at 15:25
  • @NicolBolas This is not the code I use anywhere, rather a quick implementation of some idea (as far as I remember,from Richard Smith of clang) with minor modifications. I had put a comment to remove confusion. And `std::forward` removed one confusion, but what about confusion of `T&&` as rvalue reference? When you do need to brain-parse `T&&` syntax here as forwarding reference, why not to do the same for `static_cast` as forwarding? – abir Jun 14 '13 at 05:40
  • @abir: "*When you do need to brain-parse T&& syntax here as forwarding reference, why not to do the same for static_cast as forwarding?*" Because one of those you *have* to do, and one of them you don't. More importantly, when you do it right, with `std::forward`, the code then *explains* what it's doing: forwarding the parameter. You may not understand why it uses `T&&` or how `std::forward` works, but at least it *reads* like something. – Nicol Bolas Jun 14 '13 at 05:43

2 Answers2

0
std::cout << back((int)(0),
                  (int*)(0),
                  (int**)(0),
                  (int***)(0),
                  (int****)(0),
                  (int*****)(0),
                  (int******)(0),
                  (int*******)(0),
                  1) << std::endl;

======================================================
nm -C que | fgrep eat
080489e2 W eat::eat<int*******>(int*******&&)
080489dc W eat::eat<int******>(int******&&)
080489d6 W eat::eat<int*****>(int*****&&)
080489d0 W eat::eat<int****>(int****&&)
080489ca W eat::eat<int***>(int***&&)
080489c4 W eat::eat<int**>(int**&&)
080489be W eat::eat<int*>(int*&&)
080489b8 W eat::eat<int>(int&&)
080489e2 W eat::eat<int*******>(int*******&&)
080489dc W eat::eat<int******>(int******&&)
080489d6 W eat::eat<int*****>(int*****&&)
080489d0 W eat::eat<int****>(int****&&)
080489ca W eat::eat<int***>(int***&&)
080489c4 W eat::eat<int**>(int**&&)
080489be W eat::eat<int*>(int*&&)
080489b8 W eat::eat<int>(int&&)
080489e7 W int&& back_<int*, int**, int***, int****, int*****, int******, int*******, int>::get<int>(eat, eat, eat, eat, eat, eat, eat, eat, int&&)
080489e7 W _ZN5back_IJPiPS0_PS1_PS2_PS3_PS4_PS5_iEE3getIiEEOT_3eatSB_SB_SB_SB_SB_SB_SB_SA_
n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • I don't seem to get any output containing eat, not in debug mode nor release mode (GCC 4.8.1). – Tom Knapen Jun 13 '13 at 17:28
  • Ignore my previous comment, I was using -Og in debug mode. Using no optimizations I get the same results. – Tom Knapen Jun 13 '13 at 17:33
  • @n-m I am probably wrong to say number of instantiated templates are independent of number of arguments (they do depend of number of types), however i guess they are still independent of template recursion depth usually implemented in the compiler. – abir Jun 14 '13 at 05:21
  • @abir This is true for at least g++. – n. m. could be an AI Jun 15 '13 at 10:02
0

My solution :)) compiled with gcc 4.6.4 -std=c++0x

Main idea is , for any 'i' and 0,1,2,...n-1 (where n > i) (0 ^ i), ( 1 ^ i ), ... (j ^ i) ... , ( (n-1) ^ i ) -- is unique sequence, and only value position at 'i' is zero.

It's not O(1) solution, O( log(n)) solution. But it's based on C++14 make_index_sequence. Iff compiler does compile make_index_sequence at O(1), so my solution also became O(1).

#include <cstddef>
#include <iostream>
#include <type_traits>

namespace mpl
{
    // C++14 index_sequence  struct
    template< int ... i >
    struct index_sequence
    {
        typedef index_sequence type;
        typedef int value_type;


        static constexpr std::size_t size()noexcept{ return sizeof...(i); }
    };

    namespace details
    {

    #if 1
        template< int s, typename T, typename U> struct concate_c;

        template<int s, int ...i, int ...j>
        struct concate_c< s, index_sequence<i...>, index_sequence<j...> >
                : index_sequence<i..., (j + s ) ... > {};   


        template< int s, typename T, typename U> struct concate : concate_c< s, typename T::type, typename U::type > {};

        template< int n>
        struct make_index_sequence : concate< n / 2, 
                                              make_index_sequence< n / 2 >,
                                              make_index_sequence< n - n / 2 >  
                                            >{};

   #else

   template<   typename T, typename U> struct concate_c;

        template<  int ...i, int ...j>
        struct concate_c< index_sequence<i...>, index_sequence<j...> >
                : index_sequence<i..., (j + sizeof...(i) ) ... > {};    


        template< typename T, typename U> struct concate : concate_c< typename T::type, typename U::type > {};

        template< int n>
        struct make_index_sequence : concate<  
                                              make_index_sequence< n / 2 >,
                                              make_index_sequence< n - n / 2 >  
                                            >{};
   #endif
        template<>  struct make_index_sequence<0> : index_sequence<>{};

        template<>  struct make_index_sequence<1> : index_sequence<0>{};


    } // namespace details


    template< int n> struct make_index_sequence  :  details::make_index_sequence<n>  {};

    template< typename ...Args>
    struct make_index_sequence_for : make_index_sequence< sizeof...(Args) > {};



     // helper for at_c, I - index_sequence,   
     template< typename I, typename ...p >
    struct at_ch;

     // only zero index have `type`.
    template< int i, typename T> struct id{};
    template< typename T>struct id<0,T>{ typedef T type;};

    // based from all parameters.
    template< typename ...T> struct base_all : T... {};

    template< int ... i, typename ...p>
    struct at_ch< index_sequence<i...>, p... >
    {
        struct base : base_all< id<i,p> ... > {};

        typedef typename base::type type;
    };

//  0 1 2 3 4 5 6 7 8 9
// 0: 0 1 2 3 4 5 6 7 8 9
// 1: 1 0 3 2 5 4 7 6 9 8   

    template< int i, typename I>
    struct xor_index_sequence;

    template< int i, int ...k>
    struct xor_index_sequence< i, index_sequence<k...> > : index_sequence< (k xor i) ... > {};

    template< int i, typename ...T>
    struct at_c: at_ch< 
                  typename xor_index_sequence< i,
                           typename make_index_sequence< sizeof...(T)> ::type 
                     >::type,
                   T...
                  > {};
}



int main() 
{

     typedef mpl::at_c< 2, int, double , float >::type G;
     static_assert( std::is_same<G, float>::value ,"!");

    return 0;
}
Khurshid Normuradov
  • 1,564
  • 1
  • 13
  • 20
  • Actually, I think this is O(N) in the number of types in the sequence; when `base_all` inherits from `id...`, all these `id...` need to be instantiated. – Louis Dionne Dec 18 '13 at 19:21