3

Suppose I have a std::tuple made up of types like

struct A {
  static void tip();
};

struct B {
  static void tip();
};

struct Z {
};

std::tuple<const A&,const B&,const Z&> tpl;

Yes, I need separate A, B. (The implementation of ::tip() differs for each type.) What I try to implement is a type-sensitive "visitor" that iterates through the tuple starting from the beginning to the end. Upon visiting a particular element of type T a function should be called depending on whether T has the ::tip() method or not. In the simple example of above only A and B have ::tip() implemented and Z not. So, the iterator should call twice the function for types with the ::tip() method and once the other function.

Here is what I came up with:

template< int N , bool end >
struct TupleIter
{
  template< typename T , typename... Ts >
  typename std::enable_if< std::is_function< typename T::tip >::value , void >::type
  static Iter( const T& dummy , const std::tuple<Ts...>& tpl ) {
    std::cout << "tip\n";
    std::get<N>(tpl); // do the work
    TupleIter<N+1,sizeof...(Ts) == N+1>::Iter( std::get<N+1>(tpl) , tpl );
  }

  template< typename T , typename... Ts >
  typename std::enable_if< ! std::is_function< typename T::tip >::value , void >::type
  static Iter( const T& dummy , const std::tuple<Ts...>& tpl ) {
    std::cout << "no tip\n";
    std::get<N>(tpl); // do the work
    TupleIter<N+1,sizeof...(Ts) == N+1>::Iter( std::get<N+1>(tpl) , tpl );
  }
};


template< int N >
struct TupleIter<N,true>
{
  template< typename T , typename... Ts >
  static void Iter( const std::tuple<Ts...>& tpl ) {
    std::cout << "end\n";
  }
};

I use a dummy instance of the type of the element at the iterator position and decide via enable_if which function to call. Unfortunately this doesn't work/isn't a nice solution:

  1. The compiler complains about recursive instantiation
  2. The const T& dummy is not a clean solution

I was wondering if enable_if is the right strategy to do the decision and how can one recursively iterate through the std::tuple capturing the first type and keeping all the remaining arguments in vital state. Read through How to split a tuple? but it doesn't do any decision.

How can one implement such a thing in a correct and portable way in C++11?

Community
  • 1
  • 1
ritter
  • 7,447
  • 7
  • 51
  • 84

2 Answers2

4

Well, it was harder than I expected, but this works.

Some things you were doing wrong/that I modified:

  1. You can't evaluate this: std::is_function< typename T::tip >::value, since T::tip is not a type. Even if this could be evaluated, what would happen when T::tip does not exist? Substitution would still fail.
  2. Since you use const references as your tuple's inner types, you had to clean them before trying to find the tip member inside them. By cleaning I mean removing const and removing the reference.
  3. That dummy type stuff was not a good idea, there was no need to use that parameter. You can achieve the same thing using std::tuple_element, which retrieves the i-th type from a tuple.
  4. I modified TupleIter's template parameters to the following, which means:

"TupleIter that processes the index-th type, inside a tuple of size n".

template<size_t index, size_t n> 
struct TupleIter;

The whole code is this:

#include <tuple>
#include <iostream>
#include <type_traits>

struct A {
  static void tip();
};

struct B {
  static void tip();
};

struct Z {
};

// Indicates whether the template parameter contains a static member named tip.
template<class T>
struct has_tip {
    template<class U>
    static char test(decltype(&U::tip));

    template<class U>
    static float test(...);

    static const bool value = sizeof(test<typename std::decay<T>::type>(0)) == sizeof(char);
};

// Indicates whether the n-th type contains a tip static member
template<size_t n, typename... Ts>
struct nth_type_has_tip {
    static const bool value = has_tip<typename std::tuple_element<n, std::tuple<Ts...>>::type>::value;
};

// Generic iteration
template<size_t index, size_t n>
struct TupleIter
{
  template< typename... Ts >
  typename std::enable_if< nth_type_has_tip<index, Ts...>::value , void >::type
  static Iter(const std::tuple<Ts...>& tpl) 
  {
    std::cout << "tip\n";
    TupleIter<index + 1, n>::Iter(tpl );
  }

  template< typename... Ts >
  typename std::enable_if< !nth_type_has_tip<index, Ts...>::value , void >::type
  static Iter(const std::tuple<Ts...>& tpl) {
    std::cout << "no tip\n";
    TupleIter<index + 1, n>::Iter(tpl );
  }
};

// Base class, we've reached the tuple end
template<size_t n>
struct TupleIter<n, n>
{
  template<typename... Ts >
  static void Iter( const std::tuple<Ts...>& tpl ) {
    std::cout << "end\n";
  }
};

// Helper function that forwards the first call to TupleIter<>::Iter
template<typename... Ts>
void iterate(const std::tuple<Ts...> &tup) {
    TupleIter<0, sizeof...(Ts)>::Iter(tup);
}

int main() {
    A a;
    B b;
    Z z;
    std::tuple<const A&,const B&,const Z&> tup(a,b,z);
    iterate(tup);
}
mfontanini
  • 21,410
  • 4
  • 65
  • 73
  • I've commented the code a little bit, so you can have an idea of what each helper class does. I've modified the ideone link. – mfontanini Aug 07 '12 at 12:22
  • Those are ellipses. When the compiler can call several overloads of a function, a function which takes an ellipses will be the worst match. So in this case, those are there to make the compiler always pick the first overload(the one that returns a char), since it's a better match(it's more specialized than the ellipses). – mfontanini Aug 07 '12 at 12:29
  • Why is `test<>(0)` calling `test(decltype(&U::tip))` instead of the ellipsis version? To what expands `decltype(&U::tip)` if `U==A` for example? Ohhh.. Just read you comment. Its the worst match!! And a reference to a function can be initialized with `0`. That's why it calls `char test<>`. – ritter Aug 07 '12 at 12:30
  • Exactly :D. Because it's more specialized than the ellipses one. That's why I used ellipses, *everything* is more specialized than the ellipses function. `&A::tip` is of type `void(*)()`, so `decltype(&U::tip)` is exactly that(when `U==A`). – mfontanini Aug 07 '12 at 12:32
  • @mfontanini: since there is no guarantee that ideone will keep your gist forever, could you copy/paste it in your answer (perhaps as an addendum) ? – Matthieu M. Aug 07 '12 at 13:24
  • @mfontanini Btw, you don't need to implement clean_type manually, there is already std::decay in that does the same (`remove_const`) – Arzar Aug 07 '12 at 14:51
  • @ThomasPetit great! I thought std::decay was something else. Thanks, I've fixed the code. – mfontanini Aug 07 '12 at 14:59
2

Here is another take on the question, very similar to mfontanini answer, but showcasing:

boost::fusion::for_each (instead of manually iterate over the tuple).
A variant for implementing has_type using an expression-based SFINAE approach, that I feel a little bit simpler to follow than the usual sizeof trick.

#include <boost/tuple/tuple.hpp>
#include <boost/fusion/include/boost_tuple.hpp>
#include <boost/fusion/algorithm.hpp>

#include <iostream>

    struct nat // not a type
    {
    private:
        nat(); 
        nat(const nat&);
        nat& operator=(const nat&);
        ~nat();
    };

    template <typename T>
    struct has_tip
    {
        static auto has_tip_imp(...) -> nat;

        template <typename U>
        static auto has_tip_imp(U&&) -> decltype(U::tip());

        typedef decltype(has_tip_imp(std::declval<T>())) type;
        static const bool value = !std::is_same<type, nat>::value;
    };


    struct CallTip
    {
        template<typename T>
        typename std::enable_if<has_tip<T>::value>::type
        operator()(T& t) const
        {
            std::cout << "tip\n";
            T::tip();
        }

        template<typename T>
        typename std::enable_if<!has_tip<T>::value>::type
        operator()(T& t) const
        {
            std::cout << "no tip\n";
            return;
        }
    };

struct A {
    static void tip(){}
};

struct B {
    static void tip(){}
};

struct Z {
};

int main()
{
    A a;
    B b;
    Z z;
    boost::tuple<const A&,const B&,const Z&> tpl(a, b, z);
    boost::fusion::for_each(tpl, CallTip());
}

Note that if your compiler support variadic template you can use std::tuple instead of boost::tuple inside fusion::for_each by including #include<boost/fusion/adapted/std_tuple.hpp>

Edit : As pointed by Xeo in the comment, it is possible to simplify a lot the expression-SFINAE approach by removing completely the trait has_tip and simply forward to a little call helper.
The final code is really neat and tight !

#include <boost/tuple/tuple.hpp>
#include <boost/fusion/include/boost_tuple.hpp>
#include <boost/fusion/algorithm.hpp>

#include <iostream>

struct CallTip
{
    template<typename T>
    void operator()(const T& t) const
    {
        call(t);
    }

    template<class T>
    static auto call(const T&) -> decltype(T::tip())
    { 
        std::cout << "tip\n";
        T::tip(); 
    }

    static void call(...)
    {
        std::cout << "no tip\n";
    }
};

struct A {
    static void tip(){}
};

struct B {
    static void tip(){}
};

struct Z {
};

int main()
{
    A a;
    B b;
    Z z;
    boost::tuple<const A&,const B&,const Z&> tpl(a, b, z);
    boost::fusion::for_each(tpl, CallTip());
}
Arzar
  • 13,657
  • 3
  • 25
  • 25
  • +1, I'd also recommend using the expression SFINAE approach, though maybe I wouldn't even put it in an extra trait and simply forward from the `operator()` to a little helper, like [this](http://liveworkspace.org/code/d1895eb359a59eb1f97c1a88a2857c64). – Xeo Aug 07 '12 at 15:55
  • @Xeo Beautiful ! I edited the answer because such a neat solution doesn't deserve to be buried in the comment ! I just changed the int/long overload because it bother me a little to use the "precedence mechanism" int/long. (I don't know the proper term) It seem too far-fetched and hard to understand quickly, like the original SFINAE sizeof trick was. I replaced it by the catch-all .../const T&, which seem a bit clearer. Do you know if there is some downside to this approach ? – Arzar Aug 07 '12 at 16:45
  • It's "integral promotion". ;) And the only downside I know is obviously that you need an object to pass along, instead of just passing `0`. I quite like the `int/long` approach, though, as also demonstrated [here](http://stackoverflow.com/a/9154394/500104). – Xeo Aug 07 '12 at 16:56