0

I'm trying to understand why a template partial specialization becomes invisible.

I'm doing a small example of how I reached to the error below. The example tries to overload operator<< to print to ostreams. There is a solution that works in the question 1 for printing tuples. My question is about why the one below fails with the invisible error.

The full error from clang:

call to function 'operator<<' that is neither visible in the template definition nor found by argument-dependent
      lookup
        operator<<(os, std::get<0>(t));
        ^
testing.cpp:9:47: note: in instantiation of member function 'tuple_printer<1, std::__1::tuple<std::__1::tuple<const char *, int> > >::print'
      requested here
        tuple_printer<s-1, std::tuple<T...>>::print(os, t);
                                              ^
testing.cpp:33:52: note: in instantiation of member function 'tuple_printer<2, std::__1::tuple<const char *, int> >::print' requested here
    tuple_printer<sizeof...(T), std::tuple<T...>>::print(os, t);
                                                   ^
testing.cpp:40:15: note: in instantiation of function template specialization 'operator<<<const char *, int>' requested here
    std::cout << std::make_tuple("hello", 5) << std::endl;
              ^
testing.cpp:30:15: note: 'operator<<' should be declared prior to the call site
std::ostream& operator<<(std::ostream& os, const std::tuple<T...>& t)

The example code:

#include <tuple>
#include <iostream>

template<size_t s, typename... T>
struct tuple_printer{
    static void print(std::ostream& os, const std::tuple<T...>& t){
        os << ", ";
        os << std::get<s-1>(t);
        tuple_printer<s-1, std::tuple<T...>>::print(os, t);
    }
};

template<typename... T>
struct tuple_printer<0, T...>{
    static void print(std::ostream& os, const std::tuple<T...>& t){
        //nothing to do here
    }
};

template<typename... T>
struct tuple_printer<1, T...>{
    static void print(std::ostream& os, const std::tuple<T...>& t){
        //no need for comma separator
        os << std::get<0>(t);
    }
};

template <typename... T>
std::ostream& operator<<(std::ostream& os, const std::tuple<T...>& t)
{
    os << "[";
    tuple_printer<sizeof...(T), std::tuple<T...>>::print(os, t);
    return os << "]";
}

int main()
{
    std::cout << std::make_tuple(2, 3.14159F, 2345.678) << std::endl;
    std::cout << std::make_tuple("hello", 5) << std::endl;
    std::cout << std::make_tuple() << std::endl;
    return 0;
}
Community
  • 1
  • 1
dvicino
  • 1,469
  • 1
  • 11
  • 19
  • Not related to your question, but I assume you know `template std::ostream& operator<<(std::ostream& os, const std::tuple& t)` is a bad plan? – Yakk - Adam Nevraumont Apr 09 '17 at 03:58
  • @Yakk I'm only using this as a helper for debugging some code. So, I didn't go into all the details for making the solution super generic. For example I didn't think about rvalues cases. However, I'm curious in what is your particular concern about it. I have the feeling I'm going to learn something from this :) – dvicino Apr 09 '17 at 04:39
  • [work out why this does not work](http://coliru.stacked-crooked.com/a/7ea7fd8f91a9958e) – Yakk - Adam Nevraumont Apr 09 '17 at 10:36
  • Oh, I see, this lead me to read about Koenig lookup. I knew about it, but I never knew its name :) Definitely something useful for making more specific questions in the future. Thanks @Yakk – dvicino Apr 09 '17 at 14:25

2 Answers2

2

  1. tuple_printer does not take size_t, tuple<T...> but size_t, T...:

Replace tuple_printer<s-1, std::tuple<T...>> with:

tuple_printer<s-1, T...>

and tuple_printer<sizeof...(T), std::tuple<T...>> with:

tuple_printer<sizeof...(T), T...>

  2. Furthermore you'd want the statement order in the base template to be a little different I guess:

tuple_printer<s-1, T...>::print(os, t);
os << ", ";
os << std::get<s-1>(t);
Pixelchemist
  • 24,090
  • 7
  • 47
  • 71
1
std::cout << std::make_tuple(2, 3.14159F, 2345.678)

This calls std::ostream& operator<< <int, float, double>(std::ostream& os, const std::tuple<int, float, double>& t)

From inside that you call tuple_printer<sizeof...(T), std::tuple<T...>>::print(os, t); which is

void tuple_printer<3, std::tuple<int, float, double>>::print(
    std::ostream& os,
    const std::tuple<std::tuple<int, float, double>>& t);

Note the doubled tuple. You explicitly pass a tuple as a single argument to T..., and the function then wraps that in another tuple.

Inside this print function, you call

std::get<2>(t)

which fails to compile since t only has one element.

In other contexts, get<s-1>(t) succeeds, but returns a tuple, not a fundamental element, so you then try to pass that to operator<<, but operator<< for tuples has not yet been declared.

aschepler
  • 70,891
  • 9
  • 107
  • 161