0

I have a library which expose some sort of container struct in which I want to collect diverse generic type of data which might be from the same namespace of the library as well as data from the std namespace such as array or tuple or pairs.

This container has a print_all method which will invoke operator<< for all the elements in the container. Such an operator is supposed to be provided by the user of the library.

Testing the library, I am using different template parameters for T, but I do not care too much about what is being printed by the print_all method. For the test purpose I care only that an example character is being printed no matter which T is being tested. Actually, I my real code I am using Google Test Framework and the behavior of the method under test is really asserted to be the same for every data type provided.

I tried to provide both a generic version of operator<< and two specific versions of it. You can see them in between the #if directive. None of the two compilation branches compiles correctly, most likely because of some violation of the König lookup rules. But anyway what I want to do should be easily possible somehow. What am I missing?

Here is the example code:

#include <algorithm>
#include <iostream>
#include <vector>

namespace my
{
template<typename DataType>
struct Container
{
    void print_all( std::ostream& os ) const
    {
        std::for_each(std::begin(data),
                      std::end(data),
                      [&os](const DataType& value)
                      {
                          os << value;
                      });
    }

    std::vector<DataType> data;
};

namespace test
{
struct Data
{
    std::byte data[4];
};

using Array = std::array<std::byte,4>;

#if 0

template<typename T>
std::ostream& operator<<(std::ostream& os, const T& data)
{
    return os << 'X';
}

#else

std::ostream& operator<<(std::ostream& os, const Data& data)
{
    return os << 'X';
}

std::ostream& operator<<(std::ostream& os, const Array& data)
{
    return os << 'X';
}

#endif

void run()
{  
    // Test with custom data
    { 
        Container< Data> container;
        container.data.resize(1);
        container.print_all( std::cout );
    }
    // Test with std data
    {
        Container< Array> container;
        container.data.resize(1);
        container.print_all( std::cout );
    }
}

} // namespace test
} // namespace my

int main()
{
    my::test::run();
    return 0;
}
nyarlathotep108
  • 5,275
  • 2
  • 26
  • 64
  • 1
    `template std::ostream& operator<<(std::ostream& os, const T& data)` would conflict with just about *every* other `operator<<` function. – Some programmer dude Jan 24 '20 at 11:26
  • @Someprogrammerdude clear, but why also the two specific ones will not work? Is there a way to restrict the subset for which `T` is meant? – nyarlathotep108 Jan 24 '20 at 11:27
  • @nyarlathotep108 some smart `enable_if` – bartop Jan 24 '20 at 11:27
  • Since you don't show the errors you're getting it's hard to say. If you have build-error, please always include them inside the question itself, copy-pasted (as text) in full and complete. – Some programmer dude Jan 24 '20 at 11:28
  • 1
    Here is link to CE for all tinkerers: https://godbolt.org/z/iXz7nW – bartop Jan 24 '20 at 11:30
  • If I should hazard a guess, a major issue is because when the compiler is compiling the `print_data` function, it doesn't know anything about the `Data` or `Array` types, and definitely don't have any overloaded `<<` operators that can be used. – Some programmer dude Jan 24 '20 at 11:32
  • 1
    @Someprogrammerdude btw isn't `using` just making "soft" alias to type, not triggering ADL to think this type is from his namespace? – bartop Jan 24 '20 at 11:33
  • @bartop Yes that's right, that won't work. Actually I should have remembered as I had a [very similar problem](https://stackoverflow.com/questions/59770581/cant-use-overloaded-comparison-operator-with-catch-test) just a little while ago. – Some programmer dude Jan 24 '20 at 11:36
  • @bartop `enable_if` afaik works only if you mutually exclude functions, so that in the end only one can be valid for a type. But I have no control over those `operator<<` defined in `std` which would at that point always win as soon as I `template` my test version of them. – nyarlathotep108 Jan 24 '20 at 11:51
  • @bartop totally correct: I tried with the `using` directive but ADL just ignores it. And maybe it is actually better like this for C++ language as a whole, it would really break too many things, But yet I need then another solution for this specific problem I am facing, because my example is small, but the types `T` I need to test are many. – nyarlathotep108 Jan 24 '20 at 11:59

1 Answers1

2

ADL looks into the namespaces associates with the arguments.

using Array = std::array<std::byte,4>;

this type test::Array is an alias. The namespaces associated with it for the purpose of ADL is std. test is not associated with it. ADL will only look in std. You are not permitted to add operators to std; if you violate that, your program is ill-formed, no diagnostic required. ADL cannot help you, because it only helps people who own the namespace(s) associated with the arguments.

Any << you want to support in std needs to be in namespace my, defined before your print_all function.

Your code for Data looks to me like it works, I assume your question was just poorly written, as it implies os <<Data does not work. If I am wromg amd it doesn't work, it is because of some typo; ADL works fine on structs, but not on aliases. In the future, please include complete error messages when code doesn't work.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Problem is: how can I define an `operator<<` before `print_all`, and meanwhile allow the user of the library to use the whole thing with his own provided `T`? Does this mean that the user has to define `operator<<` before including the header files of `my` lib? Wouldn't that be kinda weird? – nyarlathotep108 Jan 24 '20 at 11:42
  • @nyarla if it is the users type, they can define it in the user:s namespace. It does not have to be before `my` If it is a std type, they cannot customize `<<`. – Yakk - Adam Nevraumont Jan 24 '20 at 12:33
  • Actually in my example if I move the `operator<<` version for the `std::array` at global namespace just before the `main` function, it works. So they can indeed be overloaded. – nyarlathotep108 Jan 24 '20 at 13:00
  • @nyarl you czn add a second layer of ADL that permits custom dispatching. `void my::print_all( my::print_tag_t, std::ostream& os, T const& t ){ os< – Yakk - Adam Nevraumont Jan 24 '20 at 13:20
  • @nyar that is fragile, as << in my could block it from being seen – Yakk - Adam Nevraumont Jan 24 '20 at 13:21