5

We have dozens of string-like classes spread across dozens of namespaces, but they each have a similar structure:

namespace N::N1 {
struct S1 {
    std::string_view sv() const;
};
}

We'd like to write a template operator<< to insert these into ostreams, something like

template<typename T>
    requires requires(const T& t) {
        { t.sv() } -> std::same_as<std::string_view>;
    }
std::ostream& operator<<(std::ostream& os, const T& t) {
    os << t.sv();
    return os;
}

In order for this function template to be found by ADL, it has to either be in std:: or a copy in each of the dozens of N::N1 namespaces. I can't put it in namespace N as ADL only looks at the innermost enclosing namespace.

Is it legal to add this function template to std:: namespace? cppreference is not clear (to me) on this point. The compilers I've tried all seem to do the right thing in any case.

(C++20 if it matters)

5
  • 1
    Why not put operator<< in global? Commented Apr 17 at 4:52
  • Your case of inserting your function template into std will give undefined behaviour. No diagnostic is required for undefined behaviour, so compilers may well accept it - but the behaviour of your program will then be undefined (informally, any outcome is permitted, whether the outcome seems "right" to you or not). Commented Apr 17 at 6:06
  • Define wrapper struct for these types and write operator<< for this wrapper class: concept SvType = requires...; template <SvType T> struct Printer { const T& m; friend std::ostream& operator<<.... }; if only this form is an option: std::cout << N::Printer{s1} Commented Apr 17 at 6:44
  • 2
    what is the meaning of DTRT? Commented Apr 17 at 7:03
  • 1
    @463035818_is_not_an_ai "Do the right thing". Commented Apr 17 at 8:59

3 Answers 3

6

As already pointed out, don't add your operator<< to std. Also don't put it in the global namespace either, that's not going to help any generic code that might want to stream your types.

There's a few options here.

The obvious one is just to use a macro to define the stream operator as a hidden friend to inject it into your type. This is very straightforward and will not have any issues or ambiguities because you're defining exactly the stream operator for your type. This obviously works:

namespace N1 {
    struct S1 {
        auto sv() const -> std::string_view { return "s1"; }
        STREAM_SV(S1)
    };
}

A different one is to define that exact stream operator in a distinct namespace. That's not going to help ADL find it... unless you bring it in. Which you can:

namespace ops {
    template<typename T>
        requires requires(const T& t) {
            { t.sv() } -> std::same_as<std::string_view>;
        }
    std::ostream& operator<<(std::ostream& os, const T& t) {
        os << t.sv();
        return os;
    }
}

namespace N1 {
    struct S1 { auto sv() const -> std::string_view { return "s1"; } };
    using ops::operator<<;
}

namespace N2 {
    struct S2 { auto sv() const -> std::string_view { return "s2"; } };
    using ops::operator<<;
}

int main() {
    std::cout << N1::S1() << '\n'; // ok
    std::cout << N2::S2() << '\n'; // ok
}

A similar approach is to rely on the fact that ADL also considers namespaces of base classes, so you could add an empty ops::empty type such that this also just works (at the cost of introducing another base class, which may either be actively fine or extremely undesirable):

namespace N1 {
    struct S1 : ops::empty {
        auto sv() const -> std::string_view { return "s1"; }
    };
}
Sign up to request clarification or add additional context in comments.

Comments

2

"Is it legal to add this function template to std:: namespace?"

The general rule for Extending the namespace std is:

It is undefined behavior to add declarations or definitions to namespace std or to any namespace nested within std, with a few exceptions noted below.

(emphasis is mine)

Several exceptions are listed below that rule (like adding template specializations in certain cases), but your case does not seem to fit any of them.

Therefore I think it is not valid to add the function you mentioned to std::.
Compilers might accept it without diagnostics, but it is still undefined-behavior so there is no guarantee for the behavior of the program.

As @康桓瑋 commented above, a possible solution is to put your operator<< in global scope.
See live demo.

Comments

0

@Barry's suggestion of `using` seems a reasonable solution to me, but in the end doesn't really save much given how simple a (non-template) operator<< for S would be.

I like the idea of a base class used just to guide ADL to the right answer. I'll have to see if that is feasible in our real codebase.

The problem with putting the template in global namespace (or in N) is that works.... but only sometimes. Because this is relying on normal, non-ADL lookup, it is subject to shadowing. So if _any_ other operator<< is visible in N::N1 namespace the one in N (or global) namespace is hidden. This is very fragile and working code can be broken by completely unrelated changes, and the failure can be very context dependent (i.e. works for most usages but fails if some unrelated type N::N1::C, which happens to have an operator<< , is visible.) See this example on godbolt

As to the original question of adding to std:: namespace, it might be argued that this case might be counted under this clause (from cppreference)

It is allowed to add template specializations for any standard library function template to the namespace std only if the declaration depends on at least one program-defined type and the specialization satisfies all requirements for the original template, except where such specializations are prohibited.

as std::operator<<(std::basic_ostream<C, Trait>&, T) is already a template (for at least some types, in at least some implementations) and we are adding a partial specialisation of that.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.