c++templatesoperator-overloadingexplicit-specialization

explicit specialization for overloading operator '<<' (left shift)


Lets say i have a class, for which i want to overload an operator based on an enum type:

#include <iostream>

enum class option : char { normal, do_something_stupid };

class foo
{
public:

    int i;    
    explicit foo(int a=0) : i(a) {};
    /* overload operator '+=' based on 'option' */
    template<option E = option::normal>
    void operator+=(const foo& f) { i += f.i; }
};

/* explicit specialization for operator += */
template<> void foo::operator+=<option::do_something_stupid>(const foo& f)
{ i += (f.i +1000); }

int main()
{
    foo f1(1), f2(2);
    f1 += f2;
    std::cout << "\nf1 = " << f1.i;
    f1.operator+=<option::do_something_stupid>(f2);
    std::cout << "\nf1 = " << f1.i;

    std::cout << "\n";
    return 0;
}

This builds clean (ignoring the fact that it really does something pretty dump) both on g++ and clang++.

What if i want to overload the '<<' operator the same way? A similar approach does not seem to work:

#include <ostream>
#include <iostream>

enum class option : char { normal, do_something_stupid };

class foo
{
public:

    int i;

    explicit foo(int a=0) : i(a) {};

    template<option E = option::normal>
    friend std::ostream& operator<<(std::ostream& o, const foo& f)
    { o << f.i; return o; }
};

template<> std::ostream&
operator<< <option::do_something_stupid>(std::ostream& o, const foo& f)
{ 
    o << f.i + 1000;
    return o;
}

int main()
{
    foo f1(1), f2(2);

    std::cout << "\nf1= " << f1;
    std::cout << "\nf2= ";
    /* this triggers an error with g++ */
    std::cout.operator<< <option::do_something_stupid>(f1);

    std::cout << "\n";
    return 0;
}

According to g++, the call from main to the operator is invalid:

error: no match for ‘operator<’ (operand types are ‘<unresolved overloaded function type>’ and ‘option’)
std::cout.operator<< <option::do_something_stupid>(f1);

clang++ on the other hand, produces a different error message:

lsfov.cc:20:1: error: 'operator<<' cannot be the name of a variable or data member
operator<< <option::do_something_stupid>(std::ostream& o, const foo& f)
^
lsfov.cc:20:11: error: expected ';' at end of declaration
operator<< <option::do_something_stupid>(std::ostream& o, const foo& f)
          ^
          ;
lsfov.cc:20:12: error: expected unqualified-id
operator<< <option::do_something_stupid>(std::ostream& o, const foo& f)
           ^
lsfov.cc:33:15: error: reference to non-static member function must be called
    std::cout.operator<< <option::do_something_stupid>(f1);
    ~~~~~~~~~~^~~~~~~~~~

which goes on listing possible overload of '<<' from the standard library (if i understand correctly), like:

/usr/bin/../lib/gcc/x86_64-redhat-linux/5.3.1/../../../../include/c++/5.3.1/ostream:108:7: note: possible target for call
      operator<<(__ostream_type& (*__pf)(__ostream_type&))
      ^
/usr/bin/../lib/gcc/x86_64-redhat-linux/5.3.1/../../../../include/c++/5.3.1/ostream:117:7: note: possible target for call
      operator<<(__ios_type& (*__pf)(__ios_type&))
      ^

What is going on? Is this kind of operator specialization possible/allowed? If so, what is the proper way to call the operator? Or is clang correct and the definition is ill formed?


Solution

  • I think clang doesn't like the declaration of the friend in relation to the specialisation. Re-ordering them does the trick.

    enum class option : char { normal, do_something_stupid };
    
    // forward declare the class and operator
    class foo;
    
    template<option E = option::normal>
    std::ostream& operator<<(std::ostream& o, const foo& f);
    
    // the class with the declared friend operator
    class foo
    {
    private:
        int i;
    public:
        explicit foo(int a=0) : i(a) {};
        template<option E>
        friend std::ostream& operator<<(std::ostream& o, const foo& f);
    };
    
    // the operator implementations
    template<option E>
    std::ostream& operator<<(std::ostream& o, const foo& f)
    { o << f.i; return o; }
    
    template<> std::ostream&
    operator<< <option::do_something_stupid>(std::ostream& o, const foo& f)
    { 
        o << f.i + 1000;
        return o;
    }
    

    In addition, the operator<< used in the main is not a member cout, but rather a global.

    int main()
    {
        foo f1(1), f2(2);
    
        std::cout << "\nf1= " << f1;
        std::cout << "\nf2= ";
        /* this triggers an error with g++ */
        operator<< <option::do_something_stupid>(std::cout, f1);
    
        std::cout << "\n";
        return 0;
    }
    

    Sample here. g++ is also happy with the code as above.


    A note on operators in a non-deduced context. I assume you are using the code here in a greater project of some sort, but if the operator is being used with non-deduced parameters, it is often easier and clearer to implement the functionality in a member method or a free function (using friend as required).