When to Overload the Comma Operator?
Let's change the emphasis a bit to:
When should you overload the comma?
The answer: Never.
The exception: If you're doing template metaprogramming, operator,
has a special place at the very bottom of the operator precedence list, which can come in handy for constructing SFINAE-guards, etc.
The only two practical uses I've seen of overloading operator,
are both in Boost:
- Boost.Assign
- Boost.Phoenix – it's fundamental here in that it allows Phoenix lambdas to support multiple statements
Operator comma overloading
There is already a built-in definition for the comma operator applied to ints. Your template isn't even in the running for overload resolution, since you can't overload operators unless at least one of the arguments is a user defined type.
You could do something like this:
template<typename T>
struct vector_maker
{
std::vector<T> vec;
vector_maker& operator,(T const& rhs) {
vec.push_back(rhs);
return *this;
}
std::vector<T> finalize() {
return std::move(vec);
}
};
int main() {
auto a = (vector_maker<int>(),1,2,3,4,5).finalize();
}
Or take a look at Boost.Assign, which allows constructions like this:
std::vector<int> a;
a += 1,2,3,4,5,6,7,8;
The void(), the comma operator (operator,) and the impossible (?) overloading
The relevant clause for this is 13.3.1.2/9 [over.match.oper] in N4140:
If the operator is the operator
,
, the unary operator&
, or the operator->
, and there are no viable functions,
then the operator is assumed to be the built-in operator and interpreted according to Clause 5.
As void()
is never a valid function argument (see 5.2.2/7 [expr.call]), there never is a viable function and thus the built-in ,
will be used.
So no, what you are trying to do is not possible.
In fact, writing an iterator loop like this
for(...; ++it1, (void)++it2)
is a standard way to prevent users from breaking your code by overloading ,
for their iterator types by enforcing the built-in operator ,
to be used. (Note that I am not saying you need to do this in your everyday code. It very much depends on its actual use. This is standard library level of paranoid.)
Regarding the standard clause you linked:
The meaning of the operators =, (unary) &, and , (comma), predefined for each type, can be changed for specific class and enumeration types by defining operator functions that implement these operators.
But such a function cannot be defined because, as I said above, void()
is never a valid function argument.
Now whether or not this is an oversight/problem in the standard is open to debate.
How to overload operator to take a comma separated variable argument list
You'll have to overload the insertion operator (<<
) and the comma operator (,
) such that
mat << 1.0, 2.0;
is translated as:
mat.operator<<(1.0).operator,(2.0);
or
operator,(operator<<(mat, 1.0), 2.0);
Here's a demonstrative program that illustrates the idea without doing anything useful.
struct Foo
{
};
Foo& operator<<(Foo& f, double)
{
std::cout << "In operator<<(Foo& f, double)\n";
return f;
}
Foo& operator,(Foo& f, double)
{
std::cout << "In operator,(Foo& f, double)\n";
return f;
}
int main()
{
Foo f;
f << 10, 20, 30;
}
and its output
In operator<<(Foo& f, double)
In operator,(Foo& f, double)
In operator,(Foo& f, double)
How to overload a comma operator to assign values to an array
How do I implement the comma operator so that
obj = 1, 2, 7
will do the
exact same thing as doingobj.arr[0] = 1; obj.arr[1] = 2; obj.arr[2] = 7;
?
This would completely change the meaning of comma operator. I will prefer initializer list:
obj = {1, 2, 7};
over the usage of comma operator in this scenario.
I know from research that overloading comma operator is unusual, yet I
need to do it as it's in the requirements of the project.
Yes, I have met such teachers. I think they just want to test whether you can crack their task under these strange constraints. And my solution is based on the hidden clue in your question itself.
What I know so far is that
obj = 1, 2, 7
is equivalent to
obj.operator=(1).operator,(2).operator,(7);
Exactly. Notice how operator,
is almost synonymous to operator=
in this task:
obj.operator=(1).operator=(2).operator=(7);
So, it's just a matter of implementing this trick:
Sample& Sample::operator,(const int& val)
{
// simply reuse the assignment operator
*this = val;
// associativity of comma operator will take care of the rest
return *this;
}
Implementing operator=
is upto you.
Then you can do
obj = 1, 2, 7;
I've made a small working code similar to your example: Live Demo.
Edit:
Following Jarod's comment, which proposes a more reasonable overloading of these operators, you can overload operator=
in this way (clear + push_back):
Sample& Sample::operator=(const int& val)
{
arr[0] = val;
length = 1;
return *this;
}
And operator,
in this way (push_back):
Sample& Sample::operator,(const int& val)
{
// append the value to arr
arr[length] = val;
++length;
// associativity of comma operator will take care of the rest
return *this;
}
Putting this idea together: Demo 2
Why doesn't my overloaded comma operator get called?
void operator,(const float &rhs)
You need a const
here.
void operator,(const float &rhs) const {
cout << this->val << ", " << rhs << endl;
}
The reason is because
rhs, lhs
will call
rhs.operator,(lhs)
Since rhs
is a const comma_op&
, the method must be a const
method. But you only provide a non-const
operator,
, so the default definition will be used.
C++ comma operator overloading and vector of references
I'm using C++11 to outline you a solution (the code is tested, too), but this is doable in C++03 (using e.g. Boost.Tuple):
// Base case
template<typename Lhs, typename Rhs>
std::tuple<Lhs&, Rhs&>
operator,(Lhs& lhs, Rhs& rhs)
{
return std::tie(lhs, rhs);
}
// General case when we already have a tuple
template<typename... Lhs, typename Rhs>
std::tuple<Lhs..., Rhs&>
operator,(std::tuple<Lhs...>&& lhs, Rhs& rhs)
{
return std::tuple_cat(lhs, std::tie(rhs));
}
Usage looks like (assuming this operator resides in namespace ns
):
// Declaration: note how ff must return a tuple
std::tuple<X, Y, Z> ff(A, B, C);
A a = /* ... */;
B b = /* ... */;
C c = /* ... */;
X x; Y y; Z z;
using ns::operator,;
// brackets on the left-hand side are required
(x, y, z) = ff(a, b, c);
Here are the attached caveats:
as you've seen, you need a using declaration to bring
operator,
in scope. Even if the typesX
,Y
,Z
reside in the same scope ofoperator,
(to enable ADL),std::tuple
doesn't. You could do something liketemplate<typename... T> struct tuple: std::tuple<T...> { using std::tuple<T...>::tuple; };
(not as convenient to do in C++03 however) to have your own tuple in the appropriate namespace to have ADL in a quick-and-dirty way. However:overloaded operators must always operate on at least one user-defined type. So if types
X
andY
both happen to be types likeint
ordouble
, then you get the defaultoperator,
. The usual solution to things like this is to require the client to do instead something like(ref(x), ref(y), z) = ff(a, b, c);
whereref
will return a type in the appropriate namespace (for ADL purposes, again). Perhaps such type can be implemented in terms ofstd::reference_wrapper
(or the Boost version, for C++03) with the same quick-and-dirty hack as for the tuple. (You'd need additional overloads ofoperator,
.)
All in all, that's a lot of work (with ugly workarounds) when something like
/* declaration of ff and variable definitions the same as before */
std::tie(x, y, z) = ff(a, b, c);
or perhaps
/* this skips unnecessary default constructions of x, y, z */
auto tuple = ff(a, b, c);
using std::get;
auto& x = get<0>(tuple);
auto& y = get<1>(tuple);
auto& z = get<2>(tuple);
works out of the box (even in C++03 with Boost.Tuple). My advice in this matter would be (with no slight intended): Keep it simple, stupid! and to use that.
C++ overloading operator comma for variadic arguments
It is sort-of possible, but the usage won't look very nice. For exxample:
#include <vector>
#include <iostream>
#include <algorithm>
#include <iterator>
template <class T>
class list_of
{
std::vector<T> data;
public:
typedef typename std::vector<T>::const_iterator const_iterator;
const_iterator begin() const { return data.begin(); }
const_iterator end() const { return data.end(); }
list_of& operator, (const T& t) {
data.push_back(t);
return *this;
}
};
void print(const list_of<int>& args)
{
std::copy(args.begin(), args.end(), std::ostream_iterator<int>(std::cout, " "));
}
int main()
{
print( (list_of<int>(), 1, 2, 3, 4, 5) );
}
This shortcoming will be fixed in C++0x where you can do:
void print(const std::initializer_list<int>& args)
{
std::copy(args.begin(), args.end(), std::ostream_iterator<int>(std::cout, " "));
}
int main()
{
print( {1, 2, 3, 4, 5} );
}
or even with mixed types:
template <class T>
void print(const T& t)
{
std::cout << t;
}
template <class Arg1, class ...ArgN>
void print(const Arg1& a1, const ArgN& ...an)
{
std::cout << a1 << ' ';
print(an...);
}
int main()
{
print( 1, 2.4, 'u', "hello world" );
}
Related Topics
Is Multiplication and Division Using Shift Operators in C Actually Faster
Difference Between "If Constexpr()" VS "If()"
View Array in Visual Studio Debugger
How to Call a Function on All Variadic Template Args
Why Are Two Different Concepts Both Called "Heap"
Using Strtok With a Std::String
When Is Uint8_T ≠ Unsigned Char
Why Should Exceptions Be Used Conservatively
Order of Member Constructor and Destructor Calls
Should I Compile With /Md or /Mt
Confused When Boost::Asio::Io_Service Run Method Blocks/Unblocks
How to Construct a Std::String With an Embedded Null
Trailing Return Type Using Decltype With a Variadic Template Function
How to Display a Dynamically Allocated Array in the Visual Studio Debugger