Discussion:
How to implement Foo:swap
(too old to reply)
wij
2024-06-27 17:42:03 UTC
Permalink
template<typename T>
struct Foo {
int m_val;

void swap(Foo& ano) {
Foo tmp(ano);
ano.m_val=m_val;
m_val=tmp.m_val;
};
}

How to implement Foo:swap(..) if m_val is enum type or function pointer type?
(not using std::swap)
Bonita Montero
2024-06-27 17:53:39 UTC
Permalink
Post by wij
template<typename T>
struct Foo {
int m_val;
void swap(Foo& ano) {
Foo tmp(ano);
ano.m_val=m_val;
m_val=tmp.m_val;
};
}
How to implement Foo:swap(..) if m_val is enum type or function pointer type?
(not using std::swap)
If you don't manage any external resources you could stuck
with std::swap. Just make the copy constructor and the
copy assignment operator = default.
Bonita Montero
2024-06-27 17:57:06 UTC
Permalink
Post by Bonita Montero
Post by wij
template<typename T>
struct Foo {
  int m_val;
  void swap(Foo& ano) {
    Foo tmp(ano);
    ano.m_val=m_val;
    m_val=tmp.m_val;
  };
}
How to implement Foo:swap(..) if m_val is enum type or function pointer type?
(not using std::swap)
If you don't manage any external resources you could stuck
with std::swap. Just make the copy constructor and the
copy assignment operator = default.
Just like this:

template<typename T>
struct Foo
{
T m_val;
Foo() = default;
Foo( Foo const & ) = default;
Foo &operator =( Foo const & ) = default;
};
red floyd
2024-06-27 21:02:24 UTC
Permalink
Post by wij
template<typename T>
struct Foo {
int m_val;
void swap(Foo& ano) {
Foo tmp(ano);
ano.m_val=m_val;
m_val=tmp.m_val;
};
}
How to implement Foo:swap(..) if m_val is enum type or function pointer type?
(not using std::swap)
Exactly the same way. Why wouldn't you? And why wouldn't you use
std::swap internally? Or is this homework?
wij
2024-06-28 00:51:21 UTC
Permalink
Post by wij
template<typename T>
struct Foo {
  int m_val;
  void swap(Foo& ano) {
    Foo tmp(ano);
    ano.m_val=m_val;
    m_val=tmp.m_val;
  };
}
How to implement Foo:swap(..) if m_val is enum type or function pointer type?
(not using std::swap)
Exactly the same way.  Why wouldn't you?  And why wouldn't you use
std::swap internally?  Or is this homework?
To be specific, how to implement the swap specialization for enum type and
function pointer type (including class member function type)?

template<typename T> void swap(T&, T&) {/* omitted */}; // primiary template

template<> void swap(..) // specialization ????
red floyd
2024-06-28 04:46:58 UTC
Permalink
Post by wij
Post by wij
template<typename T>
struct Foo {
  int m_val;
  void swap(Foo& ano) {
    Foo tmp(ano);
    ano.m_val=m_val;
    m_val=tmp.m_val;
  };
}
How to implement Foo:swap(..) if m_val is enum type or function pointer type?
(not using std::swap)
Exactly the same way.  Why wouldn't you?  And why wouldn't you use
std::swap internally?  Or is this homework?
To be specific, how to implement the swap specialization for enum type and
function pointer type (including class member function type)?
template<typename T> void swap(T&, T&) {/* omitted */}; // primiary template
template<> void swap(..) // specialization ????
Enums are integral types. They'll just work.
Function pointers are just pointers. They'll just work.
red floyd
2024-06-28 04:53:48 UTC
Permalink
Post by wij
Post by wij
template<typename T>
struct Foo {
  int m_val;
  void swap(Foo& ano) {
    Foo tmp(ano);
    ano.m_val=m_val;
    m_val=tmp.m_val;
  };
}
How to implement Foo:swap(..) if m_val is enum type or function pointer type?
(not using std::swap)
Exactly the same way.  Why wouldn't you?  And why wouldn't you use
std::swap internally?  Or is this homework?
To be specific, how to implement the swap specialization for enum type and
function pointer type (including class member function type)?
template<typename T> void swap(T&, T&) {/* omitted */}; // primiary template
template<> void swap(..) // specialization ????
Given the following:

===
#include <iostream>
#include <algorithm>

enum e_t {
xxx,
yyy
};

void f()
{
std::cout << "Hello\n";
}

void g()
{
std::cout << "World\n";
}

int main()
{
void (*pf)() = &f;
void (*pg)() = &g;
pf();
pg();
std::swap(pf, pg);
pf();
pg();
e_t x = xxx;
e_t y = yyy;
std::cout << "x = " << static_cast<int>(x) << "\n";
std::cout << "y = " << static_cast<int>(y) << "\n";
std::swap(x,y);
std::cout << "x = " << static_cast<int>(x) << "\n";
std::cout << "y = " << static_cast<int>(y) << "\n";
return 0;
}
===

The output is exactly as expected:

Hello
World
World
Hello
x = 0
y = 1
x = 1
y = 0
Andrey Tarasevich
2024-06-28 18:04:22 UTC
Permalink
Post by wij
Post by wij
template<typename T>
struct Foo {
  int m_val;
  void swap(Foo& ano) {
    Foo tmp(ano);
    ano.m_val=m_val;
    m_val=tmp.m_val;
  };
}
How to implement Foo:swap(..) if m_val is enum type or function pointer type?
(not using std::swap)
Exactly the same way.  Why wouldn't you?  And why wouldn't you use
std::swap internally?  Or is this homework?
To be specific, how to implement the swap specialization for enum type and
function pointer type (including class member function type)?
template<typename T> void swap(T&, T&) {/* omitted */}; // primiary template
template<> void swap(..) // specialization ????
Firstly, C++ does not support _partial_ specializations for function
templates. So, no, you cannot literally "implement the swap
specialization for enum type and function pointer type" as long as you
are talking about _generic_ "enum type and function pointer type".

Secondly, if you are interested in _explicit_ specializations for
_specific_ enum types of function pointer types, then it is done the
same way as for any other type

enum MyEnum { A, B, C };

template<> void Foo<MyEnum>::swap(Foo &ano)
{
// whatever
}

Done.
--
Best regards,
Andrey
Paavo Helde
2024-06-27 21:04:13 UTC
Permalink
Post by wij
template<typename T>
struct Foo {
int m_val;
void swap(Foo& ano) {
Foo tmp(ano);
ano.m_val=m_val;
m_val=tmp.m_val;
};
}
template<typename T>
struct Foo {
int m_val;

void swap(Foo& ano) {
auto tmp = ano.m_val;
ano.m_val=m_val;
m_val=tmp;
}
};

But honestly, using std::swap() is 3x less lines and more readable.
Andrey Tarasevich
2024-06-28 05:21:02 UTC
Permalink
Post by wij
template<typename T>
struct Foo {
int m_val;
void swap(Foo& ano) {
Foo tmp(ano);
ano.m_val=m_val;
m_val=tmp.m_val;
};
}
How to implement Foo:swap(..) if m_val is enum type or function pointer type?
(not using std::swap)
Firstly, when implementing a custom `swap` for your custom type it is a
better idea to implement it as a friend function

template<typename T> struct Foo
{
int m_val;

friend void swap(Foo &lhs, Foo &rhs) noexcept
{
int t = lhs.m_val;
lhs.m_val = rhs.m_val;
rhs.m_val = t;
}
};

A friend-based implementation will allow one to use your type with the
`using std::swap` idiom.

Secondly, it is not clear why you decided to base your implementation on
a copy constructor. Why, really?

Thirdly, why would enum or function pointer be any different from what
you already have? What prompted the question?
--
Best regards,
Andrey
Bonita Montero
2024-06-28 08:16:38 UTC
Permalink
Post by Andrey Tarasevich
Post by wij
template<typename T>
struct Foo {
  int m_val;
  void swap(Foo& ano) {
    Foo tmp(ano);
    ano.m_val=m_val;
    m_val=tmp.m_val;
  };
}
How to implement Foo:swap(..) if m_val is enum type or function pointer type?
(not using std::swap)
Firstly, when implementing a custom `swap` for your custom type it is a
better idea to implement it as a friend function
m_val is public anyway, so there's no need for a friend.
Post by Andrey Tarasevich
  template<typename T> struct Foo
  {
    int m_val;
    friend void swap(Foo &lhs, Foo &rhs) noexcept
    {
      int t = lhs.m_val;
      lhs.m_val = rhs.m_val;
      rhs.m_val = t;
    }
  };
A friend-based implementation will allow one to use your type with the
`using std::swap` idiom.
Secondly, it is not clear why you decided to base your implementation on
a copy constructor. Why, really?
Thirdly, why would enum or function pointer be any different from what
you already have? What prompted the question?
Andrey Tarasevich
2024-06-28 16:33:36 UTC
Permalink
Post by Bonita Montero
Post by Andrey Tarasevich
Post by wij
template<typename T>
struct Foo {
  int m_val;
  void swap(Foo& ano) {
    Foo tmp(ano);
    ano.m_val=m_val;
    m_val=tmp.m_val;
  };
}
How to implement Foo:swap(..) if m_val is enum type or function pointer type?
(not using std::swap)
Firstly, when implementing a custom `swap` for your custom type it is
a better idea to implement it as a friend function
m_val is public anyway, so there's no need for a friend.
Post by Andrey Tarasevich
   template<typename T> struct Foo
   {
     int m_val;
     friend void swap(Foo &lhs, Foo &rhs) noexcept
     {
       int t = lhs.m_val;
       lhs.m_val = rhs.m_val;
       rhs.m_val = t;
     }
   };
A friend-based implementation will allow one to use your type with the
`using std::swap` idiom.
Yes, there kinda-sorta is a "need for a friend", if you take into
account the fact that the OP is actually defining a class _template_
`Foo<T>. There's no visible reason why `Foo` is a template in the OP's
code snippet, but if they want a template - so be it.

What I did in my code is I provided a non-template (!) friend function
`swap`, which will be automatically generated for every specialization
of class template `Foo<>`. The key point here is that my `swap` itself
is not a template. It is a so-called "templated entity" ("templatED" - a
concept introduced by C++11), but it is not a template itself.

C++ does not provide syntax for an out-of-class-template definition of
such a non-template function. The only way to define it is to define it
inline, directly _inside_ class-template definition as a `friend`. So,
the only reason I used `friend` here is to plant the definition of
non-member function `swap` _into_ the definition of class template `Foo<T>`.

It's a trick.

This is one of the idiomatic side-uses of `friend` specifier, which is
not related to access control at all.
--
Best regards,
Andrey
Bonita Montero
2024-06-28 21:51:25 UTC
Permalink
Post by Andrey Tarasevich
Yes, there kinda-sorta is a "need for a friend", if you take into
account the fact that the OP is actually defining a class _template_
`Foo<T>. There's no visible reason why `Foo` is a template in the OP's
code snippet, but if they want a template - so be it.
friend is unnecessary or you want to make a swap from a Foo
with a different type to be a friend to another Foo's type.
Andrey Tarasevich
2024-06-28 22:23:09 UTC
Permalink
Post by Bonita Montero
Post by Andrey Tarasevich
Yes, there kinda-sorta is a "need for a friend", if you take into
account the fact that the OP is actually defining a class _template_
`Foo<T>. There's no visible reason why `Foo` is a template in the OP's
code snippet, but if they want a template - so be it.
friend is unnecessary or you want to make a swap from a Foo
with a different type to be a friend to another Foo's type.
One more time: `friend` is absolutely necessary in order to make the
language automatically generate a freestanding _non-template_ `swap`
function for each specialization of `Foo<T>`.

As I already stated above, the language provides no other syntax for
achieving this objective besides the one based on `friend`.
--
Best regards,
Andrey
Bonita Montero
2024-06-29 10:12:10 UTC
Permalink
Post by Andrey Tarasevich
As I already stated above, the language provides no other syntax for
achieving this objective besides the one based on `friend`.
Works:

#include <iostream>

using namespace std;

template<typename T>
struct Foo
{
T m_val;
friend void swap( Foo &lhs, Foo &rhs ) noexcept;
};

template<typename T>
void swap( Foo<T> &lhs, Foo<T> &rhs ) noexcept
{
T t = lhs.m_val;
lhs.m_val = rhs.m_val;
rhs.m_val = t;
}

int main()
{
Foo<string> fsA, fsB;
::swap( fsA, fsB );
}

Without :: before swap this doesn't work.
Bonita Montero
2024-06-29 13:03:00 UTC
Permalink
Post by red floyd
Post by Andrey Tarasevich
As I already stated above, the language provides no other syntax for
achieving this objective besides the one based on `friend`.
#include <iostream>
using namespace std;
template<typename T>
struct Foo
{
    T m_val;
    friend void swap( Foo &lhs, Foo &rhs ) noexcept;
};
template<typename T>
void swap( Foo<T> &lhs, Foo<T> &rhs ) noexcept
{
    T t = lhs.m_val;
    lhs.m_val = rhs.m_val;
    rhs.m_val = t;
}
int main()
{
    Foo<string> fsA, fsB;
    ::swap( fsA, fsB );
}
Without :: before swap this doesn't work.
And usually I place the swap-code into the std-namespace, specialized
for my custom data structure so that if so explicitly uses std::swap
this still works.
Andrey Tarasevich
2024-06-29 15:59:56 UTC
Permalink
Post by Bonita Montero
And usually I place the swap-code into the std-namespace, specialized
for my custom data structure so that if so explicitly uses std::swap
this still works.
A very ugly idea.

A much better approach is the well-known ADL-based `using std::swap` idiom:

MyType a, b;
...
using std::swap;
swap(a, b); // unqualified `swap` name is used

// If `MyType` provides its own swap facilities, ADL will find
// them and use them. If `MyType` has no dedicated `swap`, the
// call will be dispatched to `std::swap`
--
Best regards,
Andrey
Bonita Montero
2024-06-29 17:04:58 UTC
Permalink
Post by Andrey Tarasevich
Post by Bonita Montero
And usually I place the swap-code into the std-namespace, specialized
for my custom data structure so that if so explicitly uses std::swap
this still works.
A very ugly idea.
It's not ugly because std::swap will never be specialized for my own
datastructure in the global namespace. And you can be assured that if
someone uses std::swap prefixed on your own data structure that this
works.
Post by Andrey Tarasevich
  MyType a, b;
  ...
  using std::swap;
  swap(a, b); // unqualified `swap` name is used
  // If `MyType` provides its own swap facilities, ADL will find
  // them and use them. If `MyType` has no dedicated `swap`, the
  // call will be dispatched to `std::swap`
Andrey Tarasevich
2024-06-29 17:27:10 UTC
Permalink
Post by Bonita Montero
Post by Andrey Tarasevich
Post by Bonita Montero
And usually I place the swap-code into the std-namespace, specialized
for my custom data structure so that if so explicitly uses std::swap
this still works.
A very ugly idea.
It's not ugly because std::swap will never be specialized for my own
datastructure in the global namespace. And you can be assured that if
someone uses std::swap prefixed on your own data structure that this
works.
That is true. If (!) someone explicitly calls `std::swap` (qualified) in
their code, your only way to "intercept" that call for your type is to
specialize `std::swap`.

But you need to take a step back and look at the bigger picture. Why
would someone call `std::swap` (qualified) in their code? There are only
two reasons to do so

1. They really wanted to call the standard algorithm
2. They are incompetent

The competent way to use `swap` is through the aforementioned `using` idiom

using std::swap;
swap(a, b); // Unqualified (!)

I.e. competent people do not qualify their `swap` calls, unless...
Unless they really-really-really want to call a very specific version of
`swap`.

So, in professional code, when you see a qualified call to `std::swap`
it normally means that the author explicitly wanted to use the standard
algorithm as defined in the standard.

You can still "intercept" and customize such calls using your method,
but it will always be a hack. Such hacks should only be reserved for
really desperate situations.
--
Best regards,
Andrey
Bonita Montero
2024-06-29 17:52:07 UTC
Permalink
Post by Andrey Tarasevich
But you need to take a step back and look at the bigger picture. Why
would someone call `std::swap` (qualified) in their code? There are only
two reasons to do so
1. They really wanted to call the standard algorithm
2. They are incompetent
3. They've preselected namespace std so this code is prefered ands they
don't need to use the ::-prefix.
Post by Andrey Tarasevich
The competent way to use `swap` is through the aforementioned `using` idiom
  using std::swap;
  swap(a, b); // Unqualified (!)
I like the std'd solution since it is the solution which requires the
user of swap the least knowledge which swap is actually used.
Post by Andrey Tarasevich
So, in professional code, when you see a qualified call to `std::swap`
it normally means that the author explicitly wanted to use the standard
algorithm as defined in the standard.
If the custom code adheres to the interface of std::swap I don't
see a problem here.
Chris M. Thomasson
2024-06-29 19:33:25 UTC
Permalink
Post by Bonita Montero
Post by Andrey Tarasevich
Post by Bonita Montero
And usually I place the swap-code into the std-namespace, specialized
for my custom data structure so that if so explicitly uses std::swap
this still works.
A very ugly idea.
It's not ugly because std::swap will never be specialized for my own
datastructure in the global namespace. And you can be assured that if
someone uses std::swap prefixed on your own data structure that this
works.
[...]

Injecting anything into the std namesapce is bad. ;^o
Chris M. Thomasson
2024-06-29 19:34:21 UTC
Permalink
Post by Chris M. Thomasson
Post by Bonita Montero
Post by Andrey Tarasevich
Post by Bonita Montero
And usually I place the swap-code into the std-namespace, specialized
for my custom data structure so that if so explicitly uses std::swap
this still works.
A very ugly idea.
It's not ugly because std::swap will never be specialized for my own
datastructure in the global namespace. And you can be assured that if
someone uses std::swap prefixed on your own data structure that this
works.
[...]
Injecting anything into the std namesapce is bad. ;^o
namespace std
{
// add things
}

No! Bad mojo.
Richard Damon
2024-06-29 20:13:54 UTC
Permalink
Post by Chris M. Thomasson
Post by Bonita Montero
Post by Andrey Tarasevich
Post by Bonita Montero
And usually I place the swap-code into the std-namespace, specialized
for my custom data structure so that if so explicitly uses std::swap
this still works.
A very ugly idea.
It's not ugly because std::swap will never be specialized for my own
datastructure in the global namespace. And you can be assured that if
someone uses std::swap prefixed on your own data structure that this
works.
[...]
Injecting anything into the std namesapce is bad. ;^o
But the Standard SPECIFICALLY allows for it, as long as the Injected
item is a specialization/overload of a defined thing using a user
defined type in the specialization/overload.
Chris M. Thomasson
2024-06-29 20:52:26 UTC
Permalink
Post by Richard Damon
Post by Chris M. Thomasson
Post by Bonita Montero
Post by Andrey Tarasevich
Post by Bonita Montero
And usually I place the swap-code into the std-namespace, specialized
for my custom data structure so that if so explicitly uses std::swap
this still works.
A very ugly idea.
It's not ugly because std::swap will never be specialized for my own
datastructure in the global namespace. And you can be assured that if
someone uses std::swap prefixed on your own data structure that this
works.
[...]
Injecting anything into the std namesapce is bad. ;^o
But the Standard SPECIFICALLY allows for it, as long as the Injected
item is a specialization/overload of a defined thing using a user
defined type in the specialization/overload.
I, personally, do not like to do that.
Bonita Montero
2024-06-29 22:38:38 UTC
Permalink
Post by Chris M. Thomasson
Post by Bonita Montero
Post by Andrey Tarasevich
Post by Bonita Montero
And usually I place the swap-code into the std-namespace, specialized
for my custom data structure so that if so explicitly uses std::swap
this still works.
A very ugly idea.
It's not ugly because std::swap will never be specialized for my own
datastructure in the global namespace. And you can be assured that if
someone uses std::swap prefixed on your own data structure that this
works.
[...]
Injecting anything into the std namesapce is bad. ;^o
Not if the specialization doesn't collide with the namespace's
definitions.
Chris M. Thomasson
2024-06-30 01:09:00 UTC
Permalink
Post by Bonita Montero
Post by Chris M. Thomasson
Post by Bonita Montero
Post by Andrey Tarasevich
Post by Bonita Montero
And usually I place the swap-code into the std-namespace, specialized
for my custom data structure so that if so explicitly uses std::swap
this still works.
A very ugly idea.
It's not ugly because std::swap will never be specialized for my own
datastructure in the global namespace. And you can be assured that if
someone uses std::swap prefixed on your own data structure that this
works.
[...]
Injecting anything into the std namesapce is bad. ;^o
Not if the specialization doesn't collide with the namespace's
definitions.
If a team I was working with is doing it, I would say, uggg, okay! Let's
go. However, personally, I don't like to inject things into the std
namespace.
Bonita Montero
2024-06-29 17:22:39 UTC
Permalink
Now it works:

#include <iostream>

using namespace std;

template<typename T>
struct Foo
{
private:
T m_val;
template<typename T2>
friend void swap( Foo<T2> &lhs, Foo<T2> &rhs ) noexcept;
};

template<typename T>
void swap( Foo<T> &lhs, Foo<T> &rhs ) noexcept
{
T t = lhs.m_val;
lhs.m_val = rhs.m_val;
rhs.m_val = t;
}

int main()
{
Foo<string> fsA, fsB;
::swap( fsA, fsB );
}

The only disadvantage with the code is that all swaps for different Ts /
T2s can access the m_val in Foo.

I've got no problem to inject a swap into std unless it is only
specified for std't types.
Andrey Tarasevich
2024-06-29 17:43:33 UTC
Permalink
Post by red floyd
#include <iostream>
using namespace std;
template<typename T>
struct Foo
{
    T m_val;
    template<typename T2>
    friend void swap( Foo<T2> &lhs, Foo<T2> &rhs ) noexcept;
};
template<typename T>
void swap( Foo<T> &lhs, Foo<T> &rhs ) noexcept
{
    T t = lhs.m_val;
    lhs.m_val = rhs.m_val;
    rhs.m_val = t;
}
int main()
{
    Foo<string> fsA, fsB;
    ::swap( fsA, fsB );
}
The only disadvantage with the code is that all swaps for different Ts /
T2s can access the m_val in Foo.
I've got no problem to inject a swap into std unless it is only
specified for std't types.
Here it is. Now you finally managed to provide a in-class friend
declaration that properly matches the out-of-class definition.

* Firstly, this has a lot more syntactic clutter.

* Secondly, as you already noted, it grants unnecessary cross-friendship
(this second problem can be solved (by adding a bit more clutter).

Now compare it with mine

template<typename T>
struct Foo
{
private:
T m_val;

friend void swap(Foo &lhs, Foo &rhs) noexcept
{
T t = lhs.m_val;
lhs.m_val = rhs.m_val;
rhs.m_val = t;
}
};

This is a lot cleaner.

Now, I'm not a big fan of multiline in-class member definitions. But, as
I said above, this `swap` cannot be defined out-of-class. The language
has no syntax for it.
--
Best regards,
Andrey
Bonita Montero
2024-06-29 17:49:10 UTC
Permalink
Post by Andrey Tarasevich
* Firstly, this has a lot more syntactic clutter.
I think my soulution is more readable and the declarative add-on doesn't
hurt.
Post by Andrey Tarasevich
* Secondly, as you already noted, it grants unnecessary cross-friendship
(this second problem can be solved (by adding a bit more clutter).
That's a theoretical problem.
Post by Andrey Tarasevich
Now compare it with mine
  template<typename T>
  struct Foo
  {
    T m_val;
    friend void swap(Foo &lhs, Foo &rhs) noexcept
    {
      T t = lhs.m_val;
      lhs.m_val = rhs.m_val;
      rhs.m_val = t;
    }
  };
This is a lot cleaner.
For me absolutely not because I like out-of-class definitions which
make the class itself more readable. Usually classes are much longer
that this counts.
Post by Andrey Tarasevich
Now, I'm not a big fan of multiline in-class member definitions. But, as
I said above, this `swap` cannot be defined out-of-class. The language
has no syntax for it.
I've shown the syntax.
Andrey Tarasevich
2024-06-29 18:03:04 UTC
Permalink
Post by Bonita Montero
I've shown the syntax.
Apparently there's no hope...

You've shown the syntax for *template* `swap`. Meanwhile, I'm stating
that there's no syntax for defining a *non-template* `swap` out-of-class.

See the difference?

We are talking about two completely different things. Yes, it is a
rather intricate and dark corner of C++ semantics, but I hoped you'd
grasp it by now.

Here's a little educational example for you (for GCC, since it uses
`__PRETTY_FUNCTION__`)

#include <iostream>

template <typename T> struct S
{
friend void foo(S &)
{ std::cout << __PRETTY_FUNCTION__ << std::endl; }
};

template <typename T> void bar(S<T> &)
{ std::cout << __PRETTY_FUNCTION__ << std::endl; }

int main()
{
S<int> s;
foo(s);
bar(s);
}

The code outputs the follwing

void foo(S<int>&)
void bar(S<T>&) [with T = int]

https://coliru.stacked-crooked.com/a/3fccf6372dfe21e7

Now, the little detail I want to draw your attention to is what
`__PRETTY_FUNCTION__` outputs for these two functions. See that `[...]`
part in the `bar`s output? Wonder why it is missing in the `foo`s output?

In our original `swap` problem I want a `foo`-like `swap`, not
`bar`-like. I want a solution that has no `[...]` in the
`__PRETTY_FUNCTION__`.
--
Best regards,
Andrey
Bonita Montero
2024-06-29 18:08:37 UTC
Permalink
Post by Andrey Tarasevich
Post by Bonita Montero
I've shown the syntax.
Apparently there's no hope...
You've shown the syntax for *template* `swap`. ...
That's a sufficient solution for the same thing.
Andrey Tarasevich
2024-06-29 18:24:30 UTC
Permalink
Post by Bonita Montero
Post by Andrey Tarasevich
Post by Bonita Montero
I've shown the syntax.
Apparently there's no hope...
You've shown the syntax for *template* `swap`. ...
That's a sufficient solution for the same thing.
It is. But that's not the issue I was talking about.
--
Best regards,
Andrey
Bonita Montero
2024-06-29 18:46:02 UTC
Permalink
Post by Andrey Tarasevich
Post by Bonita Montero
Post by Andrey Tarasevich
Post by Bonita Montero
I've shown the syntax.
Apparently there's no hope...
You've shown the syntax for *template* `swap`. ...
That's a sufficient solution for the same thing.
It is. But that's not the issue I was talking about.
For me this issue doesn't exist because I know a solution
with issues that don't hurt.
Andrey Tarasevich
2024-06-30 03:48:54 UTC
Permalink
Post by Bonita Montero
Post by Andrey Tarasevich
Post by Bonita Montero
Post by Andrey Tarasevich
Post by Bonita Montero
I've shown the syntax.
Apparently there's no hope...
You've shown the syntax for *template* `swap`. ...
That's a sufficient solution for the same thing.
It is. But that's not the issue I was talking about.
For me this issue doesn't exist because I know a solution
with issues that don't hurt.
That's fine.

In general case (also implying that we'll need non-public access), there
are three ways to define a friend to a template class

1. Non-template friend

template <typename T> class C
{
int i;
friend void foo(C &c) { c.i = 42; }
};

The definition has to be done in-class.

2. Template friend with loose friendship

template <typename T> class C
{
int i = 0;
template <typename U> friend void foo(C<U> &c) { c.i = 42; }
};

The definition can be done in-class or out-of-class.

3. Template friend with strict friendship

template <typename> class C;
template <typename T> void foo(C<T> &);

template <typename T> class C
{
int i = 0;
friend void foo<>(C &);
};

template <typename T> void foo(C<T> &c) { c.i = 42; }

The definition can only be done out-of-class, if I'm not mistaken.

You apparently prefer #2. I prefer #1.
--
Best regards,
Andrey
Andrey Tarasevich
2024-06-29 15:55:58 UTC
Permalink
Post by red floyd
Post by Andrey Tarasevich
As I already stated above, the language provides no other syntax for
achieving this objective besides the one based on `friend`.
#include <iostream>
using namespace std;
template<typename T>
struct Foo
{
    T m_val;
    friend void swap( Foo &lhs, Foo &rhs ) noexcept;
};
template<typename T>
void swap( Foo<T> &lhs, Foo<T> &rhs ) noexcept
{
    T t = lhs.m_val;
    lhs.m_val = rhs.m_val;
    rhs.m_val = t;
}
Um...

Firstly, your code is not doing what you think it is doing. The `friend`
declaration in the class has no relation to the template declaration
outside the class. (Try making the members `private` and you'll
immediately see that your template has no access to them.)

The `friend` declaration plays no role whatsoever in your code. It is
not used at all. You can simply remove it - it won't change anything.

Secondly, you implemented `swap` as a separate template. Which is a
completely different thing from what I wanted. I don't want a separate
template. I want (and I repeat): a *non-template* `swap` function to be
automatically emitted by compiler for each specialization of `Foo<>`.

Mine is a completely different approach. And, (and I repeat) this can
only be done through a `friend` hack I described above.
--
Best regards,
Andrey
Bonita Montero
2024-06-29 17:05:31 UTC
Permalink
Post by red floyd
Post by Andrey Tarasevich
As I already stated above, the language provides no other syntax for
achieving this objective besides the one based on `friend`.
#include <iostream>
using namespace std;
template<typename T>
struct Foo
{
     T m_val;
     friend void swap( Foo &lhs, Foo &rhs ) noexcept;
};
template<typename T>
void swap( Foo<T> &lhs, Foo<T> &rhs ) noexcept
{
     T t = lhs.m_val;
     lhs.m_val = rhs.m_val;
     rhs.m_val = t;
}
Um...
Firstly, your code is not doing what you think it is doing. ...
My own swap is called; I've tried that in the debugger.
Andrey Tarasevich
2024-06-29 17:35:29 UTC
Permalink
Post by Bonita Montero
My own swap is called; I've tried that in the debugger.
Yes, it is called. But what point are you trying to make?

As I stated many times previously: my objective is to provide a
dedicated *non-template* `swap` for each specialization of `Foo<>`. And
I demonstrated how one can do that (through an in class `friend`
definition).

Your code provides a *template* `swap`. This is a completely different
approach for solving the same problem.

I like my approach better.

There are objective reasons why it is better, but let's just take it as
my caprice for now. I just want a *non-template* `swap`.
--
Best regards,
Andrey
Andrey Tarasevich
2024-06-29 16:15:18 UTC
Permalink
Post by Bonita Montero
Without :: before swap this doesn't work.
This is actually what should have made you realize that your in-class
`friend` declaration and your out-of-class template `swap` declaration
are completely unrelated.

Without the `::`, the ADL finds the `friend` version of `swap`. The
unqualified lookup prefers that `friend` version. So, it calls it.
However, the `friend` version is not defined in your code. So, you end
up with a linker error.

And, as I said above, you can't define it out-of-class. The language
provides no syntax for out-of-class definition of such friends. You can
only do it inline, exactly as I do in my code sample.
--
Best regards,
Andrey
Marcel Mueller
2024-07-15 18:33:31 UTC
Permalink
Post by Andrey Tarasevich
Firstly, when implementing a custom `swap` for your custom type it is a
better idea to implement it as a friend function
Sometimes it can be useful to have a swap member function.
E.g. when implementing assignment functions/operators the pattern
Foo(whatever_arguments).swap(*this);
cannot be simply converted to a free swap function because the
constructor does does not bind the the reference argument. You always
need a named variable in this case.
Post by Andrey Tarasevich
A friend-based implementation will allow one to use your type with the
`using std::swap` idiom.
?


Marcel
Andrey Tarasevich
2024-07-16 03:04:52 UTC
Permalink
Post by Marcel Mueller
Post by Andrey Tarasevich
Firstly, when implementing a custom `swap` for your custom type it is
a better idea to implement it as a friend function
Sometimes it can be useful to have a swap member function.
E.g. when implementing assignment functions/operators the pattern
  Foo(whatever_arguments).swap(*this);
cannot be simply converted to a free swap function because the
constructor does does not bind the the reference argument. You always
need a named variable in this case.
This is an old and a well-known asymmetry in how overloaded operators
behave in C++. And fixing this asymmetry is actually one of the
side-applications of rvalue references in C++11.

Before C++11 the problem you described could also be observed in the
following example

#include <string>
#include <fstream>

int main()
{
std::string hello = "Hello";
std::ofstream("text.txt") << hello << 123 << std::endl; // 1
std::ofstream("text.txt") << 123 << hello << std::endl; // 2
}

Lines 1 and 2 look very similar. However, line 1 triggers an error
before C++11, while line 2 compiles fine.

`operator <<` for `std::string` is overloaded by a standalone function,
which expects `std::ostream &` (a non-const lvalue reference) as its
first parameter. This immediately means that a temporary object cannot
be used as its argument. For this reason sub-expression
`std::ofstream("text2.txt") << hello` is already invalid.

Meanwhile, line 2 is valid even in C++98. In this case a member overload
of `operator <<` for `int` argument is invoked first. There's no
restrictions on invoking non-const member functions for temporaries.
That invocation returns a `std::ostream &`, which can then be
successfully used as an argument for `operator <<` for `std::string`.
So, in this case the initial `<<` for `int` saves the day as an
accidental "rvalue-to-lvalue" converter.

In C++11 an additional template overload for `<<` has been added to the
library (see [ostream.rvalue])

template <class charT, class traits, class T>
basic_ostream<charT, traits>&
operator<<(basic_ostream<charT, traits>&& os, const T& x);

which simply does `os << x`, thus taking up the role of rvalue-to-lvalue
converter. Line 1 above becomes valid in C++11.
--
Best regards,
Andrey
Marcel Mueller
2024-07-20 11:19:53 UTC
Permalink
Post by Andrey Tarasevich
Post by Marcel Mueller
Sometimes it can be useful to have a swap member function.
E.g. when implementing assignment functions/operators the pattern
   Foo(whatever_arguments).swap(*this);
This is an old and a well-known asymmetry in how overloaded operators
behave in C++. And fixing this asymmetry is actually one of the
side-applications of rvalue references in C++11.
But is it really a good idea to have
swap(X&, X&&)
and
swap(X&&, X&)
?

I am not sure.


Marcel
Andrey Tarasevich
2024-07-21 03:18:37 UTC
Permalink
Post by Marcel Mueller
Post by Andrey Tarasevich
Post by Marcel Mueller
Sometimes it can be useful to have a swap member function.
E.g. when implementing assignment functions/operators the pattern
   Foo(whatever_arguments).swap(*this);
This is an old and a well-known asymmetry in how overloaded operators
behave in C++. And fixing this asymmetry is actually one of the
side-applications of rvalue references in C++11.
But is it really a good idea to have
  swap(X&, X&&)
and
  swap(X&&, X&)
?
I am not sure.
Well, yes, ultimately we'd need all 4 possible combinations. But
spelling them all out separately is cumbersome, unless you really want
to customize each implementations. If all implementations are the same,
we'd want something like

friend void swap(auto &&lhs, auto &&rhs) { ... }

with one common implementation for all cases.

The above is, of course, not the right way to go by itself, since
declaring such friend in multiple different classes will result in a
redefinition error. We have to tie the friend to its class somehow. And
that's where SFINAE/concepts come to the rescue

struct X
{
friend void swap(auto &&l, auto &&r)
requires
std::common_reference_with<decltype(l), X> &&
std::common_reference_with<decltype(r), X>
{
...
}
};

or, using the constrained placeholder syntax

struct X
{
friend void swap(std::common_reference_with<X> auto &&l,
std::common_reference_with<X> auto &&r)
{
...
}
};

This works. I wonder if I'm missing something that's still broken about
it...

P.S. On a less serious note, this kinda agrees with the tendencies we
observe in C++ these days. We used to declare

void foo(int a);

But today, it appears, we are expected to prefer the much fancier

void foo(std::same_as<int> auto x)

or

void foo(std::convertible_to<int> auto x)
--
Best regards,
Andrey
Loading...