2

Consider this (rather) simple example:

#include <iostream>

struct Out {
    int value;
};
template<class Sink> decltype(auto) operator<<(Sink &&s, Out const &out) {
    return out.value > 0? s << out.value : s;
}

struct In {
    std::ostream &sink;
    template<typename T> In &operator<<(T const &t) {
        return sink << t, *this;
    }
};

int main() {
    In in{std::cout};
    in << (1 << Out{3}) << '\n';    // Ok
    in << Out{42} << '\n';
        // error: use of overloaded operator '<<' is ambiguous
        // (with operand types 'In' and 'Out')
}

Can such an ambiguity be addressed? We have two classes, each one defines such an operator overload to forward it to its internal type (classes are designed independently by two different people, and another person tries to use them in the same application.) I don't see a way this could be reformulated in terms of other operators, say, it's no use here to give up on A's friend operator<< and try to convert A to int; and to use some kind of complicated SFINAE to rule out some overloads still doesn't look helpful.

Zoe
  • 27,060
  • 21
  • 118
  • 148
bipll
  • 11,747
  • 1
  • 18
  • 32
  • 2
    The `operator<<` for class `Out` lacks proper restrictions. Find out why it takes a templated `Sink` instead of `ostream`. Fix it. – Cheers and hth. - Alf Jul 28 '18 at 03:02
  • 2
    If I fix the obvious typo in your code it still won't compile on [Compiler Explorer](https://godbolt.org) with the latest gcc because of an inconsistent deduction for `auto`. Fixing that problem then compiles successfully without the ambiguity. What compiler/version are you using? – 1201ProgramAlarm Jul 28 '18 at 04:24
  • 1
    @1201ProgramAlarm: Ambiguous call for clang (but not for gcc !?) [Demo](http://coliru.stacked-crooked.com/a/d39642515b469081) – Jarod42 Jul 28 '18 at 07:10
  • @Cheersandhth.-Alf I just wanted to give it operator semantics transparently, simply forward the operator further to respective part of state. – bipll Jul 28 '18 at 13:12
  • @1201ProgramAlarm Thank, you practically reverted it back to how it looked like when I was creating this mincomplete. >_< For some reason (say, due to time being 4am) I've changed it while posting without even checking whether it now copmiles. – bipll Jul 28 '18 at 13:14
  • @Jarod42 Oh, really, it's still ambiguous with clang 6 but g++ accepts it, thank you. – bipll Jul 28 '18 at 13:31
  • @Cheersandhth.-Alf Apparently I only tested it with clang last night. >_> – bipll Jul 28 '18 at 13:32
  • Then the question arises, which one is closer to the truth here? – bipll Jul 28 '18 at 13:32

2 Answers2

2

You might create additional overloads which would be the better match:

decltype(auto) operator<<(In& in, Out const &out) {
    return in.operator<<(out);
}

decltype(auto) operator<<(In&& in, Out const &out) {
    return in.operator<<(out);
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Of course this solves a particular situation (as would hundreds of other approaches do) but I was thinking in general: when In and Out are taken from quite independent components and are thus agnostic of one another, can everything that may be needed be preimplemented already, without end user needing to add glue code? – bipll Jul 28 '18 at 13:58
  • Is `std::cout << Out{42}` expected to work ? if not, probably first template is too permissive. My answer mostly consider that both come from 3rd party whereas you cannot patch. so adding that code would solve the problem in a "generic" way. Upfront, each library might have create dedicated namespace which might have avoided those conflicts. – Jarod42 Jul 28 '18 at 14:29
  • Yes, it is; simply put, I'd like `Out` to be eligible for the RHS of `<<` where a simple `int` would fit in (and other operators, too.) While there is actually a special overload for `std::ostream` (that prints "Out{42}" rather than simply the number), I cannot anticipate every ostream-like class possible, but I know that bit-shift is not the only semantics for `<<` possible. – bipll Jul 28 '18 at 14:36
0

Some semi-oldschool SFINAE seemingly does the trick, in the sense that it is now accepted by both gcc and clang (and they both print "8" and "42" equally):

#include <iostream>
#include <utility>

template<typename S, typename T> class can_shl {
    using Left = char;
    struct Right { char c[2]; };
    template<typename U> static constexpr decltype(
            std::declval<S>() << std::declval<U>(), Left{}) 
        has_it(U &&);
    static constexpr Right has_it(...);
public:
    static constexpr bool value = sizeof(has_it(std::declval<T>())) == 1;
};

struct Out {
    int value;
};
template<class Sink> auto operator<<(Sink &&s, Out const &out) 
    -> std::enable_if_t<!can_shl<Sink, Out>::value, decltype(out.value > 0? s << out.value : s)> 
{   
    return out.value > 0? s << out.value : s;
}

struct In {
    std::ostream &sink;
    template<typename T> In &operator<<(T const &t) {
        return sink << t, *this;
    }
};

int main() {
    In in{std::cout};
    in << (1 << Out{3}) << '\n';    // Ok
    in << Out{42} << '\n';
}

Personally I don't feel quite confident with that "only allow this in case it does not compile on its own" approach (is this perchance a (static) UB? what would I say if I were a C++ standard?)

bipll
  • 11,747
  • 1
  • 18
  • 32
  • I think it is UB as `can_shl::value` would change after you declare the new operator. (Not sure if it is already UB or if UB requires other usage of `can_shl`)... – Jarod42 Jul 29 '18 at 00:22