2

I have a templated class, and I am trying to create a stringifier for a nested enum. I can get it compiling and working if I inline the operator<< code directly into the body of the class, but I'm trying to put it in a separate impl.hpp file, for legibility.

But, I can't get it to compile. It seems like it should be possible, but I'm stumbling on the syntax C++ requires...

Here's a small example that demonstrates the problem, along with a live link: https://godbolt.org/z/6cMn8Gf14

#include <iostream>

using std::cout;
using std::endl;

template <class T>
struct Foo {
    enum class State {
        X,Y,Z
    };

    // This version works OK:
    /*
    friend std::ostream& operator<<(std::ostream &os, State s) {
        return os << "stringify state...";
    }
    */

    // This version fails - see full error down in main().
    template <class T2>
    friend std::ostream& operator<<(std::ostream &os, typename Foo<T2>::State s);
};

template <class T>
std::ostream& operator<<(std::ostream &os, typename Foo<T>::State s) {
    return os << "stringify state...";
}

int main()
{
    auto x = Foo<int>::State::X;

/*

Error: no match for 'operator<<' ...

Details:
    note: candidate: 'template<class T> std::ostream& operator<<(std::ostream&, typename Foo<T>::State)'
            std::ostream& operator<<(std::ostream &os, typename Foo<T>::State s) {
                          ^~~~~~~~
    note:   template argument deduction/substitution failed:
    note:   couldn't deduce template parameter 'T'
                cout << "X: " << x << endl;
                                 ^
*/

    cout << "X: " << x << endl;
    
    return 0;
}

How should this be written?

jwd
  • 10,837
  • 3
  • 43
  • 67
  • 1
    Related: https://stackoverflow.com/a/25245676/12416453, [`Non-deduced contexts #1`](https://en.cppreference.com/w/cpp/language/template_argument_deduction#Non-deduced_contexts) – Ch3steR Sep 13 '21 at 18:38

1 Answers1

4

The friend functions here are just that: non-template functions stamped out by the class template. As such, there’s no way to define them en masse, although you could define individual versions like

std::ostream& operator<<(std::ostream &os, Foo<int>::State s) {…}
std::ostream& operator<<(std::ostream &os, Foo<float>::State s) {…}

Since you can’t deduce T from Foo<T>::State, this is about your only implementation option.

Davis Herring
  • 36,443
  • 4
  • 48
  • 76
  • Thanks; the key point seems to be that `Foo::State` is a "non-deduced" context, per the standard. The answer linked in Ch3steR's comment on my question has more info. I also found this other semi-workaround answer interesting: https://stackoverflow.com/questions/6060824/why-cant-the-template-argument-be-deduced-when-it-is-used-as-template-parameter/45523829#45523829 (though not exactly a solution for me). – jwd Sep 13 '21 at 21:05