Search code examples
c++c++11variadic-templatesoverload-resolution

Variadic templates: choose the tuple element type that has a proper method


I have a class like this:

template <typename... Types>
class Evaluator
{
public:
    template <typename... Types>
    Evaluator(Types... args)
    {
        list = std::make_tuple(args...);
    }

    template <typename T>
    bool Evaluate(const T& input)
    {
        // based on a specific input type T, here I want to call
        // Evaluate(input) for a specific element in the tuple. i.e. the
        // element that has method Evaluate, for which Evaluate(input) compiles 
        return std::get<0>(list).Evaluate(input);
    }

private:
    std::tuple<Types...> list;

};

Update The function could return false for instances that don't have proper "Evaluate(input) -> bool" function and is evaluated for all matching with bool result ||


Solution

  • First of all, we need a metafunction which can tell us whether the expression declval<T>().Evaluate(input) makes sense for a given type T.

    We can use SFINAE and decltype in order to do so:

    template<class ... Arguments>
    struct CanEvaluate
    {
        template<class T, class Enable = void>
        struct eval : std::false_type {};
    
        template<class T>
        struct eval<T,
            decltype( void( std::declval<T>().Evaluate(std::declval<Arguments>() ... ) ) ) > : std::true_type {};
    };
    

    Now we can write a single class MultiEvaluateFromTuple.

    template<class TupleType, class ... InputTypes>
    struct MultiEvaluateFromTuple
    {
    private:
        template<int I,int S,class Dummy = void>
        struct CheckEvaluate : CanEvaluate<InputTypes...>::template eval<typename std::tuple_element<I,TupleType>::type> {};
    
        //We need this because we can't instantiate std::tuple_element<S,TupleType>
        template<int S> struct CheckEvaluate<S,S> : std::false_type {};
    
        // Forward to the next element
        template<int I,int S, class Enabler = void>
        struct Impl {
            static bool eval(const TupleType & r, const InputTypes & ... input) {
                return Impl<I+1,S>::eval(r,input...);
            }
        };
    
        // Call T::Evalute()
        template<int I,int S>
        struct Impl<I,S, typename std::enable_if<CheckEvaluate<I,S>::value>::type> {
    
            static bool eval(const TupleType & r, const InputTypes & ... input) {
                bool Lhs = std::get<I>(r).Evaluate(input...);
                bool Rhs = Impl<I+1,S>::eval(r,input...);
                return Lhs || Rhs;
            }
        };
    
        //! Termination
        template<int S>
        struct Impl<S,S> {
            static bool eval(const TupleType & r, const InputTypes & ... input) {
                return false;
            }
        };
    
    public:
        static bool eval(const TupleType & r,const InputTypes & ... input) {
            return Impl<0, std::tuple_size<TupleType>::value>::eval(r,input...);
        }
    };
    

    Usage:

    return MultiEvaluateFromTuple<std::tuple<Types...>,T>::eval(list,input);
    

    This will call Evaluate for all the types T in Types for which CanEvaluate<InputType>::eval<T>::value == true, and return the || of the results.