8

This question is easiest to illustrate with an example, so here goes:

Is code like the following guaranteed to be valid, and compile & run correctly?

(Not all implementations actually compile it correctly, but I'm wondering if that's a bug.)

#include <algorithm>
class Picky
{
    friend
        Picky *std::copy<Picky const *, Picky *>(Picky const *, Picky const *, Picky *);
    Picky &operator =(Picky const &) { return *this; }
public:
    Picky() { }
};

int main()
{
    Picky const a;
    Picky b;
    std::copy<Picky const *, Picky *>(&a, &a + 1, &b);
    return 0;
}
Community
  • 1
  • 1
user541686
  • 205,094
  • 128
  • 528
  • 886
  • 4
    What stdlib implementation does this actually compile on? I wouldn't have thought a single one that didn't delegate `std::copy` to some internal helper existed. – Praetorian May 07 '15 at 20:54
  • @Praetorian: I haven't found any, but I could certainly write one myself that works... – user541686 May 07 '15 at 20:54
  • 2
    If it does require this to work, it's a defect in the standard. – T.C. May 07 '15 at 20:55
  • @T.C.: Why would it be? (Why) must every facility accessible to the standard library be publicly available to all callers? – user541686 May 07 '15 at 20:56
  • 3
    Sure, but you'd either be in SFINAE hell, or you'd have to forgo `memcpy` optimizations and such for trivially copyable types. Anyway, the answer to your question is certainly - no, no such guarantee exists. This is kinda the same as befriending `std::default_delete` and trying to stick a type with a private destructor in a `unique_ptr`, that's not guaranteed to work either. – Praetorian May 07 '15 at 20:57
  • 2
    @Praetorian Or befriending `make_shared` and try to make it use a private constructor. – T.C. May 07 '15 at 20:58
  • @Praetorian: I'm inclined to believe you but I'm not sure where the standard mandates that e.g. a copy-assignment operator must be publicly visible. Do you happen to have a reference? – user541686 May 07 '15 at 21:00

2 Answers2

8

std::copy requires an output iterator ([algorithms.general]/p5); output iterators, among other things, require *r = o to be valid ([output.iterators], Table 108) - not just "valid sometimes" or "valid in some contexts".

Since for Picky *p, a;, *p = a isn't valid in most contexts, Picky * isn't a valid output iterator.


Hmm it'd be great if you could generalize your answer to other things beyond the particular example I gave. Like, for example, std::vector::push_back(T const &), or whatever.

Befriending a member function is an absolute no-no, because you aren't even guaranteed that there's a member function with that signature ([member.functions]/p2, which Stephan T. Lavavej calls the "STL Implementers Can Be Sneaky Rule"):

An implementation may declare additional non-virtual member function signatures within a class:

  • by adding arguments with default values to a member function signature187 [Note: An implementation may not add arguments with default values to virtual, global, or non-member functions. — end note];
  • by replacing a member function signature with default values by two or more member function signatures with equivalent behavior; and
  • by adding a member function signature for a member function name.

187 Hence, the address of a member function of a class in the C++ standard library has an unspecified type.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • Hmm it'd be great if you could generalize your answer to other things beyond the particular example I gave. Like, for example, `std::vector::push_back(T const &)`, or whatever. – user541686 May 07 '15 at 21:03
  • Interesting edit. That has nontrivial implications -- are you saying I'm not even allowed to say `void (std::vector::*push_back)(int const &) = &std::vector::push_back;`? In other words, I can't take the addresses of member functions at all based on their signature in the standard, because they might not exist? – user541686 May 07 '15 at 21:12
  • 3
    @Mehrdad No, you can't. The standard actually calls this out in a footnote, which I added to the quote. – T.C. May 07 '15 at 21:15
  • Hmm, what about `std::swap` in C++11? Can you make `T::T(T &&)` and `T &T::operator =(T &&)` private and declare `std::swap` as a friend and expect it to work? That's neither a member function nor does it require iterators. – user541686 May 08 '15 at 00:50
  • @Mehrdad No; it requires `MoveConstructible` and `MoveAssignable`, and both requires the relevant constructs to be unconditionally valid. – T.C. May 08 '15 at 00:59
1

The code might compile if std::copy() does not call any other non-friend functions but I've yet to encounter any such implementation. And there is no requirement in the standard limiting HOW std::copy() achieves the required effect.

However, it does require a working and accessible assignment operator.

Peter
  • 35,646
  • 4
  • 32
  • 74