c++templatesc++11variadic-templatesg++-4.7

Sum helper fails for classes


I have the following sumhelper written:

template <typename T1, typename T2>
auto sum(const T1& v1, const T2& v2) -> decltype( v1 + v2) {
    return v1 + v2;
}

template <typename T1, typename T2, typename... Ts>
auto sum(const T1& v1, const T2& v2, const Ts&... rest) -> decltype( v1 + v2 + sum(rest...) ) {
    return v1 + v2 + sum(rest... );
}

Here is the CPP file

#include <iostream>
#include <type_traits>
#include "Sum.hpp" 

struct A {
    int x;

    A(const int a) : x(a) { std::cout<<x<<std::endl; };

    A &operator+(const A &tmp) const {
        std::cout<<" + "<<tmp.x<<") ";
    };
};

int main () {
    std::cout<<"sum of 1,2,3,4 is : ";

    auto ans = sum(1,2.2,3,4);

    A a(1);
    A b(2);
    A c(3);

    std::cout<<a.x;
    a+b+c;

    sum(a,b,c); //Here is syntax error

    std::cout<<ans<<std::endl;
    return 0;
}

Why am I not able to do the sum(a,b,c) ? When I have a+b+c working as demonstrate.

It gives a compile error when I pass objects but not when I pass primitive types

I am not able to understand the error template argument deduction/substitution failed.. how?


Solution

  • Here is a variation of a variardic apply_binop that takes an arbitrary operation as the first argument, and a sum wrapper that passes a binary add to it. apply_binop is better (and more clearly) known as foldr I believe:

    #include <utility>
    #include <iostream>
    
    #define RETURNS(x) ->decltype(x) { return (x); }
    
    struct add {
      template<typename T, typename U>
      auto operator()( T&& t, U&& u ) const
        RETURNS( std::forward<T>(t)+std::forward<U>(u) )
    };
    
    
    template<typename Op, typename T0>
    auto apply_binop( Op&& op, T0&& t0 )
      RETURNS(std::forward<T0>(t0))
    
    template<typename Op, typename T0, typename T1, typename... Ts>
    auto apply_binop( Op&& op, T0&& t0, T1&& t1, Ts&&... ts )
      RETURNS(
        op(
          std::forward<T0>(t0),
          apply_binop(op, std::forward<T1>(t1), std::forward<Ts>(ts)...)
        )
      )
    
    template<typename... Ts>
    auto sum( Ts&&... ts )
      RETURNS( apply_binop( add(), std::forward<Ts>(ts)... ) )
    
    int main() {
      std::cout << sum(1,2,3,4,5) << "\n";
      std::cout << sum(1) << "\n";
      std::cout << sum(1,2) << "\n";
      std::cout << sum(1,2,3) << "\n";
      std::cout << sum(1,2,3,4) << "\n";
      std::cout << sum(1,2,3,4.7723) << "\n";
    }
    

    It is foldr because it applies the binary operation to the rightmost two, then takes that result and applies it with the 3rd last, etc. foldl does the same starting from the left.

    The macro RETURNS makes up for the inability for C++ to deduce return types for single line functions (which I believe will be fixed in C++17). Getting gcc 4.7.2 to accept the above with only two apply_binop overrides took a bit of tweaking.

    Implementing foldl without 3 or more overrides is a tad trickier.

    Here is another answer where they discuss better ways to work around this issue:

    How to implement folding with variadic templates