views:

414

answers:

7

I'm looking for an easy way to build an array of strings at compile time. For a test, I put together a class named Strings that has the following members:

Strings(); 
Strings(const Strings& that);
Strings(const char* s1);
Strings& operator=(const char* s1);
Strings& operator,(const char* s2);

Using this, I can successfully compile code like this:

Strings s;
s="Hello","World!";

The s="Hello" part invokes the operator= which returns a Strings& and then the operator, get called for "World!".

What I can't get to work (in MSVC, haven't tried any other compilers yet) is

Strings s="Hello","World!";

I'd assume here that Strings s="Hello" would call the copy constructor and then everything would behave the same as the first example. But I get the error: error C2059: syntax error : 'string'

However, this works fine:

Strings s="Hello";

so I know that the copy constructor does at least work for one string. Any ideas? I'd really like to have the second method work just to make the code a little cleaner.

+10  A: 

I think that the comma in your second example is not the comma operator but rather the grammar element for multiple variable declarations.

e.g., the same way that you can write:

int a=3, b=4

It seems to me that you are essentially writing:

Strings s="Hello", stringliteral

So the compiler expects the item after the comma to be the name of a variable, and instead it sees a string literal and announces an error. In other words, the constructor is applied to "Hello", but the comma afterwards is not the comma operator of Strings.

By the way, the constructor is not really a copy constructor. It creates a Strings object from a literal string parameter... The term copy constructor is typically applied to the same type.

Uri
At risk of being tediously pedantic, `Strings s="Hello";` is equivalent to `Strings s = Strings("Hello");`. It requires the copy ctor to be callable, and might even call it, to construct `s` from a temporary object. But the optimisation to replace it with direct initialisation using the `const char*` constructor for `s`, without any temporary, is permitted and common.
Steve Jessop
Duh, can't believe I didn't see that one.
miked
+4  A: 

Use boost::list_of.

Greg Hewgill
+5  A: 

I wouldn't recommend this kind of an API. You are going to continue discovering cases that don't work as expected, since comma is the operator with the lowest precedence. For example, this case won't work either:

if ("Hello","world" == otherStrings) { ... }

You may be able to get things working if you use brackets every time around the set of strings, like this:

Strings s=("Hello","World!");

And my example above would look like this:

if (("Hello","world") == otherStrings) { ... }

That can likely be made to work, but the shorthand syntax is probably not worth the tricky semantics that come with it.

Igor ostrovsky
Can that be made to work? The problem I foresee is that you can't overload `operator,` for `const char*`, so `("Hello","world")` is just the same as `"world"`.
Steve Jessop
I assumed that you can overload operator,(const char*, const char*). Is there a reason why that can't be done? I don't do much C++ programming, to be honest.
Igor ostrovsky
A: 

You could use an array of character pointers

Strings::Strings(const char* input[]);

const char* input[] = {
  "string one",
  "string two",
  0};

Strings s(input);

and inside the constructor, iterate through the pointers until you hit the null.

sean riley
Or template the constructor: `template <int N> Strings(const char* ( i < N; ++i) push_back(input[i]); }`. Then no null terminator is required: you can do `const char *input[] = { "this", "that"}; Strings s(input);`. Not sure it's really worth it, but fun if you like that sort of thing.
Steve Jessop
Oh, or of course the constructor template could call another function similar to `vector::insert(iterator, InputIterator, InputIterator)`, to avoid duplicate code being generated if you call it with lots of different lengths in different places.
Steve Jessop
A: 

If you c++0x, they have new inializer lists for this! I wish you could use those. For example:

std::vector<std::string> v = { "xyzzy", "plugh", "abracadabra" };
std::vector<std::string> v{ "xyzzy", "plugh", "abracadabra" };
sean riley
A: 

If the only job of Strings is to store a list of strings, then boost::assign could do the job better with standard containers, I think :)

using namespace boost::assign;
vector<string> listOfThings;
listOfThings += "Hello", "World!";
AraK
A: 

It's possible to make this work, for a sufficiently loose definition of "work." Here's a working example I wrote in response to a similar question some years ago. It was fun as a challenge, but I wouldn't use it in real code:

#include <iostream>
#include <algorithm>
#include <iterator>
#include <vector>

void f0(std::vector<int> const &v) { 
    std::copy(v.begin(), v.end(), 
     std::ostream_iterator<int>(std::cout, "\t"));
    std::cout << "\n";
}

template<class T>
class make_vector {
    std::vector<T> data;
public:
    make_vector(T const &val) { 
        data.push_back(val);
    }

    make_vector<T> &operator,(T const &t) {
        data.push_back(t);
        return *this;
    }

    operator std::vector<T>() { return data; }
};

template<class T> 
make_vector<T> makeVect(T const &t) { 
    return make_vector<T>(t);
}

int main() { 
    f0((makeVect(1), 2, 3, 4, 5));
    f0((makeVect(1), 2, 3));
    return 0;
}
Jerry Coffin