Using auto with variadic templates that generates

2019-08-18 08:23发布

问题:

I have been working on this for some time and I found this Q/A as a good answer into how I can store a tuple. Now I'm trying to use a function template that will generate this class and the auto key word to generate instances of this object. I'm not getting any compiler errors; yet it isn't generating any data and I can not figure out where I'm going wrong, however, my ostream<<() is generating compiler errors complaining about std::get

Here is my class and a few ways of how I'm trying to use it.

#include <algorithm>
#include <iostream>
#include <tuple>

template<class... T>
class expression_t {
public:
    std::tuple<T...> rhs;
    std::size_t size = sizeof...(T);        

    template<class... Args>
    expression_t(Args&& ...args) : rhs( std::forward<Args>(args)... ){}    
    std::tuple<T...> operator()() {
        return hrs;
    }
};

template<typename... Args>
expression_t<Args...> expression(Args... args) {
    expression_t<Args...> expr(args...);
    return expr;
}

template<typename... Args>
std::ostream& operator<< (std::ostream& os, const expression_t<Args...>& expr) {            

    for (std::size_t n = 0; n < expr.size; n++ ) {
        if ( std::get<n>(expr.rhs) == '+' || std::get<n>(expr.rhs) == '-' || 
             std::get<n>(expr.rhs) == '*' || std::get<n>(expr.rhs) == '/' || 
             std::get<n>(expr.rhs) == '%')
             os << ' ' << std::get<n>(expr.rhs) << ' ';
        os << std::get<n>(expr.rhs);
    }
    os << '\n';

    return os;
}

int main() {
    double x = 0;
    // example: 4x^2 + 2x
    auto expr = expression( 4, x, '^', 2, '+', 2, x );

    // try to print a single element from expr's tuple member
    auto t = expr(); // using operator()        
    std::cout << std::get<2>(t); // compiles and runs but does not work

    // try to print out the expression
    std::cout << expr; // the ostream<<() operator fails to compile
    // it is complaining about `std::get` with no matching overloaded function found 
    // with MSVC error C2672

    return 0;
}



Edit


I took Igor's advice and tried to use cppreference's example found here and this is what I have come up with for my operator<<().

template<class Ch, class Tr, class Tuple, std::size_t... Is>
void print_expression_tuple_impl(std::basic_ostream<Ch, Tr>& os, const Tuple& t, std::index_sequence<Is...>) {
    if ( (std::get<Is>(t) == '+') || 
         (std::get<Is>(t) == '-') ||
         (std::get<Is>(t) == '*') || 
         (std::get<Is>(t) == '/') ||
         (std::get<Is>(t) == '%') )
        os << " " << std::get<Is>(t) << " ";
    os << std::get<Is>(t);
}

template<class Ch, class Tr, class... Args>
auto& operator<<(std::basic_ostream<Ch,Tr>& os, const std::tuple<Args...>& t) {         
    print_expression_tuple_impl(os, t, std::index_sequence_for<Args...>{});
    return os;
}

template<class... Args>
std::ostream& operator<<(std::ostream& os, const expression_t<Args...>& expr) {
    return os << expr.rhs << '\n';
}

This complains that Is needs to be expanded, okay so I try to expand it in the print... function and I've tried placing the ... operator in multiple places and nothing seems to compile. I'm not sure how to expand Is in this context, or if I can even use fold expressions.

回答1:

As mentioned in the comments, non-type template parameters need compile-time constant expressions. That’s why std::get cannot be used the way you did.

If you want to iterate over the elements of a tuple, I would recommend to use std::apply, which is specifically designed to do so. A possible reimplementation of your code snipped would be:

#include <algorithm>
#include <iostream>
#include <tuple>

template<class... T>
class expression_t {
public:
    std::tuple<T...> rhs;
    std::size_t size = sizeof...(T);        

    template<class... Args>
    expression_t(Args&& ...args) : rhs( std::forward<Args>(args)... ){}    

    std::tuple<T...> operator()() const { // Needs to be const to be used by the operator <<
        return rhs;
    }
};

template <typename T>
void Print(std::ostream& os, T x) {
    os << x;
}

template <>
void Print<char>(std::ostream& os, char x) {
if ( x == '+' || x == '-' ||  x == '*' || x == '/' || x == '%')
  os << ' ' << x << ' ';
}

template<typename... Args>
expression_t<Args...> expression(Args... args) {
    expression_t<Args...> expr(args...);
    return expr;
}

template<typename... Args>
std::ostream& operator <<(std::ostream& os, const expression_t<Args...>& expr) {            

    auto Fn = [&os](auto... x) {
      (Print(os, x), ...);                 // Fold over a comma
    };

    std::apply(Fn, expr());

    os << '\n';

    return os;
}


int main() {
    double x = 0;
    // example: 4x^2 + 2x
    auto expr = expression( 4, x, '^', 2, '+', 2, x );

    auto t = expr();
    std::cout << std::get<2>(t) << '\n'; // Prints ^ as expected

    std::cout << expr;

    return 0;
}