Question

I tried to write a template class and output operator to it as following:

#include <iostream>

namespace N
{

template< typename ...types >
struct X
{
    static_assert((sizeof...(types) != 0), "zero length");
    X() = default;
    X(X const &) = default;
    X(X &&) = default;
    template< typename ...args >
    //explicit // does not matter
    X(args &&...) { ; }
    int value = 10;
};

template< typename ...types >
std::ostream &
operator << (std::ostream & out, X< types... > const & x)
{
    return out << x.value;
}

} // namespace N

int main()
{
    using namespace N;
    X< float > /*const*/ x; // `const` does not matter
    std::cout << x << std::endl;
    return 0;
}

But static_assertion raised:

main.cpp:9:5: error: static_assert failed "zero length"
    static_assert((sizeof...(types) != 0), "zero length");
    ^             ~~~~~~~~~~~~~~~~~~~~~~~
main.cpp:32:23: note: in instantiation of template class 'N::X<>' requested here
    std::cout << x << std::endl;
                      ^
1 error generated.

If class template X and operator << overloading defined in global namespace then all is the same. I found out, that commenting using namespace N; line and substitution X< float > to N::X< float > solves the problem.

How to explain such behaviour? What is the cause?

EDIT:

I find the solution: is to chage template parameters of operator << overloading as follows:

template< typename first, typename ...rest >
std::ostream &
operator << (std::ostream & out, X< first, rest... > const & x)
{
    return out << x.value;
}

Splitting typename ..types of the class is not nessesarily. Moreover, it is not desirable at all, due to extreme bloating of code as consequences.

Was it helpful?

Solution

A simple way to reproduce your issue:

int main()
{
    using namespace N;
    std::cout << std::endl;
}

In this case, candidate functions are all overloads of operator<< from namespace std, all member operator<<'s from std::ostream, and your function template operator<< from namespace N.

13.3.1/7: "where a candidate is a function template, candidate function template specializations are generated using template argument deduction"

So, before overload resolution can begin, X<types...> const& has to be deduced from std::endl, which is an address of a template function. Address of a function is a function pointer type, and the only way to match N::X<types...> const& to a pointer type is to deduce types... as an empty list.

(substitution of course fails since there is no implicit conversion from any function pointer type to N::X<>, which would have eliminated the overload quietly as non-viable, but the static assert is not in immediate context and is a hard error)

The moral of the story: using-directives are evil.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top