0

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.

Francis Cugler
  • 7,788
  • 2
  • 28
  • 59
  • 1
    `std::get` shouldn't compile. Only a compile-time constant can be passed as a template argument. You can't loop over tuple elements this way. – Igor Tandetnik Jun 21 '19 at 03:36
  • @IgorTandetnik Okay, but not sure how to do that... sort of new to tuples. – Francis Cugler Jun 21 '19 at 03:38
  • @IgorTandetnik Outside of looping through the tuple, if I comment out the `operator<<()` and I try to use `auto` and get the tuple from it, it's still not generating any values. – Francis Cugler Jun 21 '19 at 03:39
  • `std::cout << std::get<2>(t)` [works for me](https://rextester.com/NTVG90492). Prints `^` – Igor Tandetnik Jun 21 '19 at 03:41
  • @IgorTandetnik odd I'm using Visual Studio 2017 set to latest draft standard. – Francis Cugler Jun 21 '19 at 03:42
  • [Works for me](https://rextester.com/FZW15531) with MSVC, too, though I'm not sure which version exactly. – Igor Tandetnik Jun 21 '19 at 03:43
  • See [`integer_sequence`](https://en.cppreference.com/w/cpp/utility/integer_sequence). The example at the bottom is doing almost exactly what you are trying to. – Igor Tandetnik Jun 21 '19 at 03:45
  • @IgorTandetnik Okay I had some other stuff running in main before my `expression class` I had to comment that out... It is working now. So I'm confident that my class construction is good. Now I can move on to the `operator<<()` – Francis Cugler Jun 21 '19 at 03:47
  • @IgorTandetnik Okay I'll have to look at `ingter_sequence` and experiment with it, but for some that are specific `characters` such as `operators` I'd like to append space before and after, otherwise if it is a number or some other character just print it. – Francis Cugler Jun 21 '19 at 03:50
  • @IgorTandetnik I could of easily made this class store a string member as a rhs expression, however I'm experimenting with tuples for practice. – Francis Cugler Jun 21 '19 at 03:51
  • In the example, a zeroth element is formatted one way and all others a different way. It should be simple to adapt this to test a condition other than `index == 0`. – Igor Tandetnik Jun 21 '19 at 03:52
  • @IgorTandetnik I took your advice and tried to use cppreference's example and I posted what I've come up with but I'm still having issues with it compiling. – Francis Cugler Jun 21 '19 at 04:38
  • The code utilizing `Is` has to be a single expression, followed by `, ...` (this last one tells the compiler "and now evaluate this expression once for every integer in the sequence"). If you cannot express your logic in one expression, break it out into a separate function and call that from `print_expression_tuple_impl` – Igor Tandetnik Jun 21 '19 at 12:05

1 Answers1

2

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;
}
metalfox
  • 6,301
  • 1
  • 21
  • 43
  • When I get some time I'll have to check this out. I know what fold expressions are, but never really used them so they are kind of new to me I appreciate your feed back and a working example. – Francis Cugler Jun 21 '19 at 17:17
  • Hmm; I tried your example, now in my code they are defined in a header file and they are in a namespace, but I'm now getting a `LNK2005` build error stating that: `void __cdecl math::Print(class std::basic_ostream > &,char)` has already been defined in `*.obj` – Francis Cugler Jun 21 '19 at 17:44
  • I had started a new Q/A here: https://stackoverflow.com/q/56709825/1757805 – Francis Cugler Jun 21 '19 at 20:08
  • @FrancisCugler By the way, I believe that you meant `expression( 4, '*', x, '^', 2, '+', 2, '*', x );` to get a readable output. In this regard, the character '^' should also be considered in the `if` statement. – metalfox Jun 25 '19 at 07:03
  • The character `^` is part of a mathematical term of a polynomial: `4x^3 - 3x^2` I want the grouping to be based on each term or monomial of the original polynomial expression, therefore, `3x^2` is grouped without spaces then a spacing before and after the basic operators `+`, `-`, `*`, `/`, and `%`, although in basic math or algebraic expressions you rarely see `%`, but in programming it shows up more often and it would separate terms. – Francis Cugler Jun 25 '19 at 08:13