4

Given the following code:

#include <iostream>
#include <functional>
#include <utility>

template<class O, class T, class = void>
constexpr bool ostreamable_with = false;

template<class O, class T> // If there's user-defined overloads
constexpr bool ostreamable_with<
    O, T, std::void_t<decltype(operator<<(std::declval<O>(),
                                          std::declval<T>()))>> = true;

struct jostream : std::reference_wrapper<std::ostream>
{
    using reference_wrapper::reference_wrapper;
    std::ostream& os() { return *this; }

    template<class T>
    jostream& operator<<(T const& v)
    {
        if constexpr(ostreamable_with<jostream&, T const&>)
            // This enables user-defined conversion on `v` too
            operator<<(*this, v); // #1
        else
            os() << v;

        return *this;
    }
};

namespace user {
    struct C
    { int a; };

    inline jostream& operator<<(jostream& os, C const& c)
    { return os << c.a; }
}

int main()
{
    jostream jos(std::cout);
    user::C u{1};
    jos << std::cref(u);
}

In line #1, there's a compiler error because jostream has a function member called operator<<, thus the call in line #1 (the commented line inside jostream::operator<<, not the first line of the code) is trying to make a explicit call to jostream::operator<< with two parameters, which doesn't exist.

Is there any trick to force a call to a non-member function with colliding names? (apart from calling an external function that makes the actual call). A ::operator<< call is clearly not a solution here because the overload could be inside a user namespace, as the example shows.

(using gcc-7.2.0)

ABu
  • 10,423
  • 6
  • 52
  • 103
  • Line #1 is `#include ` – mnistic Oct 28 '17 at 01:27
  • @mnistic Haha, there's a comment with a line number, inside `jostream::operator<<`. – ABu Oct 28 '17 at 01:28
  • Can't reproduce, http://rextester.com/GAA51213 – Stargateur Oct 28 '17 at 01:53
  • @Stargateur You need to activate `-std=c++17`, but clang has incomplete support. Test it here: http://coliru.stacked-crooked.com/a/3acda05699399c1f – ABu Oct 28 '17 at 01:58
  • @Peregring-lk well c++17 is not yep official, I use c++1z with clang. Plus my gcc don't compile like yours it's would be usefull to put what compiler version you use in your question cause you use unstable feature. – Stargateur Oct 28 '17 at 02:03
  • @Stargateur Anyway, the question has nothing to do with any C++-17 feature. I use C++-17 features to implement the context where I'm in a shorter way. – ABu Oct 28 '17 at 02:05

1 Answers1

3
using std::operator<<;
operator<<(*this, v);

std is an associated namespace of *this anyway, so this doesn't introduce anything new into the overload set. Alternatively, define a namespace-scope operator<< taking some dummy type and pull that in with using.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • :o but what is the relationship between `std::operator<<` and `user::operator<<`? What is the process to go from `std` to `user`, if non of both arguments are inside the `std` namespace? I'm very surprised that this works. – ABu Oct 28 '17 at 02:53
  • @Peregring-lk I'm also wondering about this. It's apparent that a dummy overload (declaration) of `operator<<` in `user` ([e.g. as in this gist](https://gist.github.com/dfrib/8190fed53dfc223da17ec248aff901d6)) will allow non-qualified lookup in the `user` namespace for the `operator<<(*this, v);` call, but I don't entirely follow how `using std::operator<<` would allow non-qualified lookup in `user` as this namespace is parallell to `std`, but apparently it works. I speculate, however, that the since `jostream` is declared in global scope, and we allow unqualified lookup in the accessible ... – dfrib Oct 28 '17 at 12:08
  • ... (sub-)namespace `std`, unqualified lookup will also consider the parallell (sub-)namespaces such as `user`, as this is also accessible from `::` (i.e., from the level of `jostream` declaration). But I can't say ... T.C.? – dfrib Oct 28 '17 at 12:13
  • @dfri I think the thing is that, once you have polluted the lookup space with a non-member function, ADL (which is initially not-considered if a member-function appear in normal unqualified lookup), is activated and the user-defined overload is found through the parameter. The T.C. trick I think is to activate ADL lookup using the `std` namespace, because in normal "ADL mode", the `std` namespace is already considered since `jostream` inherits from `std::reference_wrapper`. So, `using std::operator<<` brings nothing new to the overload set but activates ADL. – ABu Oct 28 '17 at 15:36
  • What I want to know is the rules governing "ADL activation" and fully understand what is the overloaded set with and without that using declaration. – ABu Oct 28 '17 at 15:47