I think the issue is that Foo is incomplete when you're declaring the friend, so I think it's impossible. I just tried it and g++ ignores the target candidate due to "member access into incomplete type", which makes sense since std::begin is already defined and calls .begin(). The closest you can get is to use another friend to expose arr and overload std::begin manually, but that's a bit silly 😅
After trying different combinations, it seems that I managed to get it working with the condition the whole template are considered friends of the class. I don't know if I should consider it a language problem, but it seems that way, since the template restrictions (in this case) are minor.
template <typename T>
auto do_something(T &t) -> decltype(t.private_msg);
class Foo
{
private:
const char *private_msg = "You can't touch me!";
template <typename T>
friend auto do_something(T &t) -> decltype(t.private_msg); // This works fine!
};
template <>
auto do_something<Foo>(Foo &f) -> decltype(f.private_msg)
{
return f.private_msg;
}
Ah, nice idea. I've tried a few different ways of doing this, and I think what you're seeing is a discrepancy in how the compiler handles member access into incomplete types. It seems that, in your examples, the compiler is allowing -> decltype(f.private_msg) within the class, but I think it's not selecting do_something outside of it because it uses decltype(t.private_msg). In my case, I'm not even able to do that within the class.
For example, since I'm not able to use decltype(f.private_msg) inside the class, I'm using decltype(private_msg) instead, which causes an error at the do_something declaration related to incomplete type (presumably because of the t.private_msg usage):
// candidate template ignored; member access into incomplete type
template 〈class T〉 auto do_something(T &t) -> decltype(t.private_msg);
class Foo {
const char *private_msg = "You can't touch me!";
friend auto do_something〈〉(Foo &f) -> decltype(private_msg);
};
template 〈〉 auto do_something(Foo &f) -> decltype(f.private_msg) {
return f.private_msg;
}
My reasoning is that removing the t.private_msg from the declaration works:
template 〈class Ret, class T〉 auto do_something(T &t) -> Ret;
class Foo {
const char *private_msg = "You can't touch me!";
friend auto do_something〈〉(Foo &f) -> decltype(private_msg);
};
template 〈〉 auto do_something(Foo &f) -> decltype(f.private_msg) {
return f.private_msg;
}
static Foo foo{};
// this works, but Ret cannot be deduced and must be specified somehow:
static auto something = do_something〈const char*〉(foo);
The reason your second example works is because the friend template inside the class acts as a template declaration rather than a specialization, which isn't specialized until after Foo is complete:
// the do_something inside Foo is a declaration, meaning this isn't used
// template 〈class T〉
// auto do_something(T &t) -> decltype(t.private_msg);
class Foo {
const char *private_msg = "You can't touch me!";
template 〈class T〉 // t.private_msg is allowed because T is not Foo yet
friend auto do_something(T &t) -> decltype(t.private_msg);
};
template 〈〉 auto do_something(Foo &f) -> decltype(f.private_msg) {
return f.private_msg;
}