6

I have a gut-feeling VS2012 is wrong on this one, but I'm not certain.

After looking this question, I felt like trying to implement something similar.

My version works fine on Visual Studio 2012, but does not even compile on Ideone.

Here is my main interface:

#include <iostream>
#include <string>

template <class In, class Out>
struct Pipe
{
    typedef In in_type ;
    typedef Out out_type ;

    In in_val ;

    Pipe (const in_type &in_val = in_type()) : in_val (in_val)
    {
    }

    virtual auto operator () () const -> out_type
    {
        return out_type () ;
    }
};

template <class In, class Out, class Out2>
auto operator>> (const Pipe <In, Out> &lhs, Pipe <Out, Out2> &rhs) -> Pipe <Out, Out2>&
{
    rhs = lhs () ;
    return rhs ;
}

template <class In, class Out>
auto operator>> (const Pipe <In, Out> &lhs, Out &rhs) -> Out&
{
    rhs = lhs () ;
    return rhs ;
}

Here are a few test classes:

struct StringToInt : public Pipe <std::string, int>
{
    StringToInt (const std::string &s = "") : Pipe <in_type, out_type> (s)
    {
    }

    auto operator () () const -> out_type
    {
        return std::stoi (in_val) ;
    }
};

struct IntSquare : public Pipe <int, int>
{
    IntSquare (int n = 0) : Pipe <in_type, out_type> (n)
    {
    }

    auto operator () () const -> out_type
    {
        return in_val * in_val ;
    }
};

struct DivideBy42F : public Pipe <int, float>
{
    DivideBy42F (int n = 0) : Pipe <in_type, out_type> (n)
    {
    }

    auto operator () () const -> out_type
    {
        return static_cast <float> (in_val) / 42.0f ;
    }
};

And here's the driver:

int main ()
{
    float out = 0 ;
    StringToInt ("42") >> IntSquare () >> DivideBy42F () >> out ;
    std::cout << out << "\n" ;

    return 0 ;
}

Ideone is complaining about template deductions and its unable to find the correct operator>> candidate function:

prog.cpp: In function ‘int main()’:
prog.cpp:75:21: error: no match for ‘operator>>’ (operand types are ‘StringToInt’ and ‘IntSquare’)
  StringToInt ("42") >> IntSquare () >> DivideBy42F () >> out ;
                     ^
prog.cpp:75:21: note: candidates are:
prog.cpp:23:6: note: Pipe<Out, Out2>& operator>>(const Pipe<In, Out>&, Pipe<Out, Out2>&) [with In = std::basic_string<char>; Out = int; Out2 = int]
 auto operator>> (const Pipe <In, Out> &lhs, Pipe <Out, Out2> &rhs) -> Pipe <Out, Out2>&
      ^
prog.cpp:23:6: note:   no known conversion for argument 2 from ‘IntSquare’ to ‘Pipe<int, int>&’
prog.cpp:30:6: note: template<class In, class Out> Out& operator>>(const Pipe<In, Out>&, Out&)
 auto operator>> (const Pipe <In, Out> &lhs, Out &rhs) -> Out&
      ^
prog.cpp:30:6: note:   template argument deduction/substitution failed:
prog.cpp:75:35: note:   deduced conflicting types for parameter ‘Out’ (‘int’ and ‘IntSquare’)
  StringToInt ("42") >> IntSquare () >> DivideBy42F () >> out ;

Which compiler is correct? If Ideone is correct, is there any easy fix to this code?

Community
  • 1
  • 1
jliv902
  • 1,648
  • 1
  • 12
  • 21
  • 3
    Related to (*maybe duplicate?*): [Non-const reference bound to temporary, Visual Studio bug?](http://stackoverflow.com/q/16380966/1708801) ... basically gcc is correct here. If you build with `/Za` in Visual Studio it will fail too, [see it live](http://rextester.com/GYMOJ23021). – Shafik Yaghmour Oct 07 '14 at 20:36
  • 3
    It is silly to vote to close this question due *Why isn't this code working*. The test could be smaller but it is a complete test case with conflicting results between compilers and it is definitely not obvious to most which is correct. – Shafik Yaghmour Oct 07 '14 at 20:43

2 Answers2

2

The first template basically fails because you can't bind a prvalue temporary - IntSquare () - to a non-const lvalue reference.

no known conversion for argument 2 from ‘IntSquare’ to ‘Pipe<int, int>&’

This says that you can't initialize Pipe<int, int>& with a prvalue of type IntSquare. The value category is unfortunately not explicitly mentioned in the error message. Although this is a standard rule VC++ ignores it to ease (or troublesome) a C++-programmers daily life.

The second template

template <class In, class Out>
auto operator>> (const Pipe <In, Out> &lhs, Out &rhs) -> Out&
{
    rhs = lhs () ;
    return rhs ;
}

fails because for two different deductions of Out two different types were deduced - the first being int (for lhs) and the second being IntSquare.

Columbo
  • 60,038
  • 8
  • 155
  • 203
2

Ideone (actually, GCC) is correct here. In Visual Studio, it compiles because of an infamous extension which allows temporaries to bind to non-const lvalue references (the standard forbids that).

I see several possible ways to solve this in standard C++:

One, don't use temporaries for the pipeline stages:

int main ()
{
    float out = 0 ;
    StringToInt stage1("42");
    IntSquare stage2;
    DivideBy24F stage3;
    stage1 >> stage2 >> stage3 >> out ;
    std::cout << out << "\n" ;

    return 0 ;
}

Two, create a "stay" function (opposite of std::move), and use that:

template <class T>
T& stay(T &&x) { return x; }

int main ()
{
    float out = 0 ;
    stay(StringToInt ("42")) >> stay(IntSquare ()) >> stay(DivideBy42F ()) >> out ;
    std::cout << out << "\n" ;

    return 0 ;
}

Three, provide an overload of operator >> taking an r-value reference:

template <class In, class Out, class Out2>
auto operator>> (const Pipe <In, Out> &&lhs, Pipe <Out, Out2> &&rhs) -> Pipe <Out, Out2>&
{
    return lhs >> rhs;  // Notice that lhs and rhs are lvalues!
}

Of course, ideally you'd provide mixed &, && and &&, & overloads as well.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455