Variable initialization fails to compile with comma-operator

230 Views Asked by At

Why does the line indicated ( in main() ) in the following code not compile?

#include <iostream>
#include <string>
#include <map>

template< typename _T > struct Inventory : public std::map< _T, int >
{
    bool getat(int i, _T &t, int &n)
    {
        if ((i < 0) || (i >= (int)this->size())) return false;
        int c=0;
        typename std::map< _T, int >::iterator it = this->begin();

        while ((c < i) && (it != this->end())) { c++; it++; }
        t = (*it).first;
        n = (*it).second;
        return true;
    }
    Inventory &operator=(_T t) { (*this)[t]++; return *this; }
    Inventory &operator,(_T t) { return operator=(t); }
};

int main()
{
    int i=0, quantity;
    std::string item;

    //***Fails to compile:***
    //Inventory< std::string > inv = "a","b","c","a","c","d","e";  

    Inventory< std::string > inv;
    inv = "a","b","c","a","c","d","e";    //but this is fine
    inv = "e","f";

    while (i < (int)inv.size())
    {
        if (inv.getat(i, item, quantity)) 
                std::cout << i << ": " << item << " -> " << quantity << "\n";
        i++;
    }
    return 0;
}
2

There are 2 best solutions below

6
On BEST ANSWER

That's called copy-initialization. In short, it uses the conversion constructor and then the copy constructor to construct inv, not operator = as you expect.

Inventory< std::string > inv = "a","b","c","a","c","d","e";  

is invalid syntax. Something like Inventory< std::string > inv = "a" would first attempt to create a temporary Inventory from "a","b","c","a","c","d","e" and then use that temporary as argument to a copy constructor to construct inv. operator = is never called in this case.

Your second version works because

Inventory< std::string > inv;

calls the default constructor, and

inv = "a","b","c","a","c","d","e"; 

calls operator = on an already initialized object.

1
On

You're problem is that in one case, the comma is punctuation (and operator overloading doesn't apply), and in the other, it is an operator. The definition that doesn't work is basically the equivalent of:

Inventory<std::string> inv = "a";
Inventory<std::string> "b";
Inventory<std::string> "c";
//  ...

Because of this, overloading operator, is always bad design; when the comma is an operator, and when it isn't, is too subtle.

The usual way of doing something like this is to create a related class to collect the arguments, and pass it into the constructor:

Inventory<std::string> inv = Collector().add("a").add("b")...;

You could also overload an operator and use it, instead of the function add. But I don't see a likely operator (<<, inspired by ostream?) Or you could overload the constructor and the operator() of Collector, and write:

Inventory<std::string> inv = Collector("a")("b")...;

You would then use the same syntax for assignment. (You really don't want something that works for assignment, and not for copy construction.)